Use correct status codes for failed deletes, and support multi delete in
JPA
This commit is contained in:
parent
aa32e6f0a9
commit
e7e8f8dd81
|
@ -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" })
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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?"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
File diff suppressed because it is too large
Load Diff
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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}">
|
||||
|
|
Loading…
Reference in New Issue