Use correct status codes for failed deletes, and support multi delete in

JPA
This commit is contained in:
James Agnew 2015-10-02 18:18:54 -04:00
parent aa32e6f0a9
commit e7e8f8dd81
20 changed files with 3861 additions and 576 deletions

View File

@ -909,6 +909,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
myCriterionList.add((ICriterionInternal) theCriterion);
return this;
}
@Override
public IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType) {
Validate.notNull(theResourceType, "theResourceType can not be null");
myCriterionList = new CriterionList();
myResourceType = myContext.getResourceDefinition(theResourceType).getName();
return this;
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.gclient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.model.api.IResource;
@ -43,8 +44,15 @@ public interface IDelete {
IDeleteTyped resourceConditionalByUrl(String theSearchUrl);
/**
* Delete using a conditional/match URL. The query parameters will be added in the next part of the call chain.
* @since HAPI 0.9 / FHIR DSTU 2
*/
IDeleteWithQuery resourceConditionalByType(String theResourceType);
/**
* Delete using a conditional/match URL. The query parameters will be added in the next part of the call chain.
* @since HAPI 1.3
*/
IDeleteWithQuery resourceConditionalByType(Class<? extends IBaseResource> theResourceType);
}

View File

@ -73,6 +73,10 @@ public abstract class BaseServerResponseException extends RuntimeException {
private String myResponseMimeType;
private int myStatusCode;
public static void main (String[] args) {
BaseServerResponseException.class.getName();
}
/**
* Constructor
*

View File

@ -129,6 +129,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
@ -149,6 +150,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
@Autowired
private PlatformTransactionManager myPlatformTransactionManager;
@Autowired
private DaoConfig myDaoConfig;
private String myResourceName;
private Class<T> myResourceType;
private String mySecondaryPrimaryKeyParamName;
@ -1309,7 +1313,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
throw new InvalidRequestException("Trying to delete " + theId + " but this is not the current version");
}
validateOkToDeleteOrThrowPreconditionFailedException(entity);
validateOkToDeleteOrThrowResourceVersionConflictException(entity);
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theId, theId.getResourceType());
@ -1332,26 +1336,31 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
if (resource.isEmpty()) {
throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "unableToDeleteNotFound", theUrl));
} else if (resource.size() > 1) {
throw new ResourceNotFoundException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
if (myDaoConfig.isAllowMultipleDelete() == false) {
throw new PreconditionFailedException(getContext().getLocalizer().getMessage(BaseHapiFhirDao.class, "transactionOperationWithMultipleMatchFailure", "DELETE", theUrl, resource.size()));
}
}
Long pid = resource.iterator().next();
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
validateOkToDeleteOrThrowPreconditionFailedException(entity);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
ActionRequestDetails requestDetails = new ActionRequestDetails(idToDelete, idToDelete.getResourceType());
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
// Perform delete
Date updateTime = new Date();
ResourceTable savedEntity = updateEntity(null, entity, true, updateTime, updateTime);
notifyWriteCompleted();
ourLog.info("Processed delete on {} in {}ms", theUrl, w.getMillisAndRestart());
return toMethodOutcome(savedEntity, null);
for (Long pid : resource) {
ResourceTable entity = myEntityManager.find(ResourceTable.class, pid);
validateOkToDeleteOrThrowResourceVersionConflictException(entity);
// Notify interceptors
IdDt idToDelete = entity.getIdDt();
ActionRequestDetails requestDetails = new ActionRequestDetails(idToDelete, idToDelete.getResourceType());
notifyInterceptors(RestOperationTypeEnum.DELETE, requestDetails);
// Perform delete
Date updateTime = new Date();
updateEntity(null, entity, true, updateTime, updateTime);
notifyWriteCompleted();
}
ourLog.info("Processed delete on {} (matched {} resource(s)) in {}ms", new Object[] {theUrl, resource.size(), w.getMillisAndRestart()});
return new DaoMethodOutcome();
}
private DaoMethodOutcome doCreate(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTime) {
@ -2512,7 +2521,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
}
}
protected void validateOkToDeleteOrThrowPreconditionFailedException(ResourceTable theEntity) {
protected void validateOkToDeleteOrThrowResourceVersionConflictException(ResourceTable theEntity) {
TypedQuery<ResourceLink> query = myEntityManager.createQuery("SELECT l FROM ResourceLink l WHERE l.myTargetResourcePid = :target_pid", ResourceLink.class);
query.setParameter("target_pid", theEntity.getId());
query.setMaxResults(1);
@ -2526,7 +2535,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
String sourceId = link.getSourceResource().getIdDt().toUnqualifiedVersionless().getValue();
String sourcePath = link.getSourcePath();
throw new PreconditionFailedException(
throw new ResourceVersionConflictException(
"Unable to delete " + targetId + " because at least one resource has a reference to this resource. First reference found was resource " + sourceId + " in path " + sourcePath);
}

View File

@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
public class DaoConfig {
private boolean myAllowMultipleDelete;
private int myHardSearchLimit = 1000;
private int myHardTagListLimit = 1000;
private int myIncludeLimit = 2000;
@ -74,6 +75,14 @@ public class DaoConfig {
return mySubscriptionPollDelay;
}
public Long getSubscriptionPurgeInactiveAfterMillis() {
return mySubscriptionPurgeInactiveAfterMillis;
}
public boolean isAllowMultipleDelete() {
return myAllowMultipleDelete;
}
/**
* See {@link #setSubscriptionEnabled(boolean)}
*/
@ -81,6 +90,10 @@ public class DaoConfig {
return mySubscriptionEnabled;
}
public void setAllowMultipleDelete(boolean theAllowMultipleDelete) {
myAllowMultipleDelete = theAllowMultipleDelete;
}
public void setHardSearchLimit(int theHardSearchLimit) {
myHardSearchLimit = theHardSearchLimit;
}
@ -144,10 +157,6 @@ public class DaoConfig {
mySubscriptionPollDelay = theSubscriptionPollDelay;
}
public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) {
setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND);
}
public void setSubscriptionPurgeInactiveAfterMillis(Long theMillis) {
if (theMillis != null) {
Validate.exclusiveBetween(0, Long.MAX_VALUE, theMillis);
@ -155,8 +164,8 @@ public class DaoConfig {
mySubscriptionPurgeInactiveAfterMillis = theMillis;
}
public Long getSubscriptionPurgeInactiveAfterMillis() {
return mySubscriptionPurgeInactiveAfterMillis;
public void setSubscriptionPurgeInactiveAfterSeconds(int theSeconds) {
setSubscriptionPurgeInactiveAfterMillis(theSeconds * DateUtils.MILLIS_PER_SECOND);
}
}

View File

@ -49,6 +49,7 @@ import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.validation.DefaultProfileValidationSupport;
@ -105,11 +106,11 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
final ResourceTable entity = readEntityLatestVersion(theId);
OperationOutcome oo = new OperationOutcome();
try {
validateOkToDeleteOrThrowPreconditionFailedException(entity);
validateOkToDeleteOrThrowResourceVersionConflictException(entity);
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Ok to delete");
} catch (PreconditionFailedException e) {
} catch (ResourceVersionConflictException e) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setDiagnostics(e.getMessage());
throw new PreconditionFailedException(e.getMessage(), oo);
throw new ResourceVersionConflictException(e.getMessage(), oo);
}
return new MethodOutcome(new IdDt(theId.getValue()), oo);
}

View File

@ -28,12 +28,14 @@ import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceSearchParam;
import ca.uhn.fhir.model.dstu2.valueset.ConditionalDeleteStatusEnum;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
@ -48,11 +50,13 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
private IFhirSystemDao<Bundle> mySystemDao;
private volatile Conformance myCachedValue;
private RestfulServer myRestfulServer;
private DaoConfig myDaoConfig;
public JpaConformanceProviderDstu2(RestfulServer theRestfulServer, IFhirSystemDao<Bundle> theSystemDao) {
public JpaConformanceProviderDstu2(RestfulServer theRestfulServer, IFhirSystemDao<Bundle> theSystemDao, DaoConfig theDaoConfig) {
super(theRestfulServer);
myRestfulServer = theRestfulServer;
mySystemDao = theSystemDao;
myDaoConfig = theDaoConfig;
super.setCache(false);
}
@ -63,17 +67,23 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
Map<String, Long> counts = mySystemDao.getResourceCounts();
FhirContext ctx = myRestfulServer.getFhirContext();
retVal = super.getServerConformance(theRequest);
for (Rest nextRest : retVal.getRest()) {
for (RestResource nextResource : nextRest.getResource()) {
ConditionalDeleteStatusEnum conditionalDelete = nextResource.getConditionalDeleteElement().getValueAsEnum();
if (conditionalDelete == ConditionalDeleteStatusEnum.MULTIPLE_DELETES_SUPPORTED && myDaoConfig.isAllowMultipleDelete() == false) {
nextResource.setConditionalDelete(ConditionalDeleteStatusEnum.SINGLE_DELETES_SUPPORTED);
}
// Add resource counts
Long count = counts.get(nextResource.getTypeElement().getValueAsString());
if (count != null) {
nextResource.addUndeclaredExtension(false, ExtensionConstants.CONF_RESOURCE_COUNT, new DecimalDt(count));
}
// Add chained params
for (RestResourceSearchParam nextParam : nextResource.getSearchParam()) {
if (nextParam.getTypeElement().getValueAsEnum() == SearchParamTypeEnum.REFERENCE) {
@ -86,7 +96,7 @@ public class JpaConformanceProviderDstu2 extends ServerConformanceProvider {
}
}
}
}
}

View File

@ -69,8 +69,8 @@ public class JpaResourceProviderDstu2<T extends IResource> extends BaseJpaResour
}
}
@Delete
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalUrlParam String theConditional) {
@Delete()
public MethodOutcome delete(HttpServletRequest theRequest, @IdParam IdDt theResource, @ConditionalUrlParam(supportsMultiple=true) String theConditional) {
startRequest(theRequest);
try {
if (theConditional != null) {

View File

@ -97,6 +97,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
@ -683,7 +684,7 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
try {
myOrganizationDao.delete(orgId);
fail();
} catch (PreconditionFailedException e) {
} catch (ResourceVersionConflictException e) {
assertThat(e.getMessage(), containsString("Unable to delete Organization/" + orgId.getIdPart()
+ " because at least one resource has a reference to this resource. First reference found was resource Patient/" + patId.getIdPart() + " in path Patient.managingOrganization"));
}
@ -839,6 +840,8 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
}
@Test
public void testHistoryByForcedId() {
IIdType idv1;
@ -2088,21 +2091,21 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(Patient.SP_RES_ID));
pm.setSort(new SortSpec(BaseResource.SP_RES_ID));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(Patient.SP_RES_ID).setOrder(SortOrderEnum.ASC));
pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.ASC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(idMethodName, id1, id2, id3, id4));
pm = new SearchParameterMap();
pm.add(Patient.SP_IDENTIFIER, new TokenParam("urn:system", methodName));
pm.setSort(new SortSpec(Patient.SP_RES_ID).setOrder(SortOrderEnum.DESC));
pm.setSort(new SortSpec(BaseResource.SP_RES_ID).setOrder(SortOrderEnum.DESC));
actual = toUnqualifiedVersionlessIds(myPatientDao.search(pm));
assertEquals(5, actual.size());
assertThat(actual, contains(id4, id3, id2, id1, idMethodName));

View File

@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu2ValidateTest.class);
@ -141,9 +142,9 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
OperationOutcome outcome=null;
try {
myOrganizationDao.validate(null, orgId, null, null, ValidationModeEnum.DELETE, null);
fail();
} catch (PreconditionFailedException e) {
myOrganizationDao.validate(null, orgId, null, null, ValidationModeEnum.DELETE, null);
fail();
} catch (ResourceVersionConflictException e) {
outcome= (OperationOutcome) e.getOperationOutcome();
}

View File

@ -671,7 +671,7 @@ public class FhirSystemDaoDstu2Test extends BaseJpaDstu2Test {
try {
mySystemDao.transaction(request);
fail();
} catch (ResourceNotFoundException e) {
} catch (PreconditionFailedException e) {
assertThat(e.getMessage(), containsString("resource with match URL \"Patient?"));
}
}

View File

@ -101,7 +101,7 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
restServer.setPlainProviders(mySystemProvider);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(restServer, mySystemDao);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(restServer, mySystemDao, myDaoConfig);
confProvider.setImplementationDescription("THIS IS THE DESC");
restServer.setServerConformanceProvider(confProvider);

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@ import org.springframework.web.context.WebApplicationContext;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
@ -80,7 +81,7 @@ public class JpaServerDemo extends RestfulServer {
setServerConformanceProvider(confProvider);
} else {
IFhirSystemDao<Bundle> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription("Example Server");
setServerConformanceProvider(confProvider);
}

View File

@ -14,6 +14,7 @@ import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu1;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
@ -83,7 +84,7 @@ public class TestRestfulServer extends RestfulServer {
systemProviderDstu2 = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
etagSupport = ETagSupportEnum.ENABLED;
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, myAppCtx.getBean(DaoConfig.class));
confProvider.setImplementationDescription(implDesc);
setServerConformanceProvider(confProvider);
baseUrlProperty = "fhir.baseurl.dstu2";

View File

@ -37,9 +37,11 @@ public class SubscriptionPlaygroundController extends BaseController {
Bundle resp = client
.search()
.forResource(Subscription.class)
.where(Subscription.TYPE.exactly().code(SubscriptionChannelTypeEnum.WEBSOCKET.getCode()))
.and(Subscription.STATUS.exactly().code(SubscriptionStatusEnum.ACTIVE.getCode()))
// .where(Subscription.TYPE.exactly().code(SubscriptionChannelTypeEnum.WEBSOCKET.getCode()))
// .and(Subscription.STATUS.exactly().code(SubscriptionStatusEnum.ACTIVE.getCode()))
.returnBundle(Bundle.class)
.sort().descending(Subscription.TYPE)
.sort().ascending(Subscription.STATUS)
.execute();
//@formatter:off

View File

@ -17,6 +17,7 @@
<property name="subscriptionEnabled" value="true"></property>
<property name="subscriptionPurgeInactiveAfterSeconds" value="3600" /> <!-- 1 hour -->
<property name="subscriptionPollDelay" value="5000"></property>
<property name="allowMultipleDelete" value="true"/>
</bean>
<util:list id="myServerInterceptors">

View File

@ -30,96 +30,108 @@
</div>
<div class="panel-body">
<div class="container-fluid">
<p>
This page is a test playground for WebSocket Subscriptions
</p>
<p>This page is a test playground for WebSocket
Subscriptions</p>
</div>
</div>
</div>
<!-- Subscription Creation -->
<div id="subscription_creation">
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#wss"
aria-controls="wss" role="tab" data-toggle="tab">Static
Websocket</a></li>
<li role="presentation"><a href="#wsd" aria-controls="wsd"
role="tab" data-toggle="tab">Dynamic Websocket</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<!-- Static Websocket -->
<div role="tabpanel" class="tab-pane active" id="wss">
<table class="table table-striped table-condensed">
<tr>
<td>ID</td>
<td>Criteria</td>
<td></td>
</tr>
<tr th:each="subscription : ${subscriptions}">
<td th:text="${subscription.id.toUnqualified()}"/>
<td th:text="${subscription.criteria}"/>
<td>
<button type="button" class="btn btn-default btn-primary btn-block" id="dws_create" th:onclick="'dwsCreate(\'' + ${subscription.id.getIdPart()} + '\');'"><i class="glyphicon glyphicon-cog"></i> Subscribe</button>
</td>
</tr>
</table>
<!-- Nav tabs -->
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#wss"
aria-controls="wss" role="tab" data-toggle="tab">Static
Websocket</a></li>
<li role="presentation"><a href="#wsd" aria-controls="wsd"
role="tab" data-toggle="tab">Dynamic Websocket</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<!-- Static Websocket -->
<div role="tabpanel" class="tab-pane active" id="wss">
<table class="table table-striped table-condensed">
<tr>
<td>ID</td>
<td>Criteria</td>
<td>Type</td>
<td>Status</td>
<td></td>
</tr>
<tr th:each="subscription : ${subscriptions}">
<td><a th:href="${subscription.id}" th:text="${subscription.id.toUnqualifiedVersionless().getIdPart()}"/>
</td>
<td th:text="${subscription.criteria}" style="max-width: 200px;"/>
<td th:text="${subscription.channel.type}" />
<td th:text="${subscription.status}" />
<td>
<button th:if="${subscription.channel.type} == 'websocket' AND ${subscription.status} == 'active'" type="button" class="btn btn-default btn-primary btn-block" id="dws_create" th:onclick="'dwsCreate(\'' + ${subscription.id.getIdPart()} + '\');'"> <i class="glyphicon glyphicon-cog"></i> Subscribe</button>
</td>
</tr>
</table>
<p th:if="${subscriptions.isEmpty()}">There are
currently no subscriptions on this server.</p>
</div>
<!-- Dynamic WebSocket -->
<div role="tabpanel" class="tab-pane" id="wsd">
<div class="row">
<div class="col-sm-12">Enter a criteria in the text box
below and then click subscribe to create a dynamic
subscription and then display the results as they arrive.</div>
</div>
<div class="row" style="boorder-top: 10px;">
<div class="col-sm-7">
<div class="input-group">
<div class="input-group-addon">Criteria</div>
<input class="form-control"
placeholder="e.g: Observation?patient=123" id="dws_criteria" />
</div>
</div>
<!-- Dynamic WebSocket -->
<div role="tabpanel" class="tab-pane" id="wsd">
<div class="row">
<div class="col-sm-12">
Enter a criteria in the text box below and then click subscribe to
create a dynamic subscription and then display the results as they
arrive.
</div>
</div>
<div class="row" style="boorder-top: 10px;">
<div class="col-sm-7">
<div class="input-group">
<div class="input-group-addon">
Criteria
</div>
<input class="form-control" placeholder="e.g: Observation?patient=123" id="dws_criteria"/>
</div>
</div>
<div class="col-sm-3">
<button type="button" class="btn btn-default btn-primary btn-block" id="dws_create" onclick="dwsCreate(document.getElementById('dws_criteria').value, true);"><i class="glyphicon glyphicon-cog"></i> Subscribe</button>
</div>
</div>
<div class="col-sm-3">
<button type="button"
class="btn btn-default btn-primary btn-block" id="dws_create"
onclick="dwsCreate(document.getElementById('dws_criteria').value, true);">
<i class="glyphicon glyphicon-cog"></i> Subscribe
</button>
</div>
</div>
</div>
<!-- Subscription Results -->
<div class="panel panel-default" style="display:none;" id="resultsDiv">
<div class="panel-heading">Subscription Results</div>
<ul class="list-group" id="subscription_status_list">
</ul>
</div>
</div>
</div>
</div>
<script type="text/javascript">
<!-- Subscription Results -->
<div class="panel panel-default" style="display: none;"
id="resultsDiv">
<div class="panel-heading">Subscription Results</div>
<ul class="list-group" id="subscription_status_list">
</ul>
</div>
</div>
<script type="text/javascript">
function messageStatus(message) {
var listItem = $('<li />', { 'class': 'list-group-item' });
var listItem = $('<li />', {
'class' : 'list-group-item'
});
listItem.append($('<div class="glyphicon glyphicon-info-sign" style="width: 20px;"/>'));
listItem.append($('<span>' + message + '</span>'));
$('#subscription_status_list').append(listItem);
}
function messageIn(message, remainder) {
var listItem = $('<li />', { 'class': 'list-group-item' });
var listItem = $('<li />', {
'class' : 'list-group-item'
});
listItem.append($('<div class="glyphicon glyphicon-download" style="width: 20px;"/>'));
listItem.append($('<span>' + message + '</span>'));
if (remainder) {
@ -128,9 +140,9 @@
}
$('#subscription_status_list').append(listItem);
}
function dwsCreate(bindArgument, dynamic) {
$('#resultsDiv').css("display","");
$('#resultsDiv').css("display", "");
$('#subscription_creation').hide();
try {
var url = "ws://fhirtest.uhn.ca/websocket/dstu2";
@ -139,15 +151,12 @@
socket.onopen = function() {
messageStatus("Connected to: " + url);
$('#subscription_status_list')
.append(
$('<li />', { 'class': 'list-group-item' })
.append($('<div class="glyphicon glyphicon-upload" style="width: 20px;"/>'))
.append($('<span>bind ' + bindArgument + '</span>'))
);
$('#subscription_status_list').append($('<li />', {
'class' : 'list-group-item'
}).append($('<div class="glyphicon glyphicon-upload" style="width: 20px;"/>')).append($('<span>bind ' + bindArgument + '</span>')));
socket.send("bind " + bindArgument);
}
// This function is called when a new message comes from the server
socket.onmessage = function(msg) {
var data = msg.data;
@ -166,10 +175,10 @@
}
return false;
}
</script>
</script>
</div>
</div>
</div>
</form>

View File

@ -15,6 +15,7 @@
<bean id="myFhirContext${versionCapitalized}" class="ca.uhn.fhir.context.FhirContext" factory-method="for${versionCapitalized}"/>
<bean id="mySystemDao${versionCapitalized}" class="ca.uhn.fhir.jpa.dao.FhirSystemDao${versionCapitalized}">
<property name="context" ref="myFhirContext${versionCapitalized}"/>
</bean>
@ -23,6 +24,7 @@
</bean>
#if ( ${versionCapitalized} == 'Dstu2' )
<bean id="myFhirContextDstu2Hl7Org" class="ca.uhn.fhir.context.FhirContext" factory-method="forDstu2Hl7Org"/>
<jpa:repositories base-package="ca.uhn.fhir.jpa.dao.data" />
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
@ -41,7 +43,7 @@
#foreach ( $res in $resources )
<bean id="my${res.name}Dao${versionCapitalized}"
## Some resource types have customized DAOs for resource specific functionality
#if ( ${versionCapitalized} == 'Dstu2' && ( ${res.name} == 'Bundle' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'ValueSet'))
#if ( ${versionCapitalized} == 'Dstu2' && ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'ValueSet'))
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}">
#else
class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}">