JPA server now has configurable properties that allow referential integrity to be disabled for both writes and deletes. This is useful in some cases where data integrity is not wanted or not possible. It can also be useful if you want to delete large amounts interconnected data quickly.
This commit is contained in:
parent
a834770e38
commit
1ec180628f
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3;
|
|||
|
||||
public class RunServerCommand extends BaseCommand {
|
||||
|
||||
private static final String DISABLE_REFERENTIAL_INTEGRITY = "disable-referential-integrity";
|
||||
private static final String OPTION_LOWMEM = "lowmem";
|
||||
private static final String OPTION_ALLOW_EXTERNAL_REFS = "allow-external-refs";
|
||||
private static final int DEFAULT_PORT = 8080;
|
||||
|
@ -49,6 +50,7 @@ public class RunServerCommand extends BaseCommand {
|
|||
options.addOption(OPTION_P, "port", true, "The port to listen on (default is " + DEFAULT_PORT + ")");
|
||||
options.addOption(null, OPTION_LOWMEM, false, "If this flag is set, the server will operate in low memory mode (some features disabled)");
|
||||
options.addOption(null, OPTION_ALLOW_EXTERNAL_REFS, false, "If this flag is set, the server will allow resources to be persisted contaning external resource references");
|
||||
options.addOption(null, DISABLE_REFERENTIAL_INTEGRITY, false, "If this flag is set, the server will not enforce referential integrity");
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
|
|
|
@ -9,6 +9,7 @@ public class ContextHolder {
|
|||
|
||||
private static boolean ourAllowExternalRefs;
|
||||
private static FhirContext ourCtx;
|
||||
private static boolean ourDisableReferentialIntegrity;
|
||||
private static String ourPath;
|
||||
|
||||
public static FhirContext getCtx() {
|
||||
|
@ -25,6 +26,10 @@ public class ContextHolder {
|
|||
return ourAllowExternalRefs;
|
||||
}
|
||||
|
||||
public static boolean isDisableReferentialIntegrity() {
|
||||
return ourDisableReferentialIntegrity;
|
||||
}
|
||||
|
||||
public static void setAllowExternalRefs(boolean theAllowExternalRefs) {
|
||||
ourAllowExternalRefs = theAllowExternalRefs;
|
||||
}
|
||||
|
@ -43,5 +48,9 @@ public class ContextHolder {
|
|||
|
||||
ourCtx = theCtx;
|
||||
}
|
||||
|
||||
public static void setDisableReferentialIntegrity(boolean theDisableReferentialIntegrity) {
|
||||
ourDisableReferentialIntegrity = theDisableReferentialIntegrity;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -168,6 +168,8 @@ public class JpaServerDemo extends RestfulServer {
|
|||
|
||||
DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class);
|
||||
daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs());
|
||||
daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity());
|
||||
daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity());
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu3Interceptor() {
|
||||
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu2Interceptor() {
|
||||
return new RestHookSubscriptionDstu2Interceptor();
|
||||
}
|
||||
|
||||
|
|
|
@ -378,6 +378,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
try {
|
||||
valueOf = translateForcedIdToPid(typeString, id);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
if (myConfig.isEnforceReferentialIntegrityOnWrite() == false) {
|
||||
continue;
|
||||
}
|
||||
String resName = getContext().getResourceDefinition(type).getName();
|
||||
throw new InvalidRequestException("Resource " + resName + "/" + id + " not found, specified in path: " + nextPathsUnsplit);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
|||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
|
@ -79,6 +80,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
protected PlatformTransactionManager myPlatformTransactionManager;
|
||||
@Autowired
|
||||
private IResourceHistoryTableDao myResourceHistoryTableDao;
|
||||
@Autowired
|
||||
private IResourceLinkDao myResourceLinkDao;
|
||||
private String myResourceName;
|
||||
@Autowired
|
||||
protected IResourceTableDao myResourceTableDao;
|
||||
|
@ -87,8 +90,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
protected IFulltextSearchSvc mySearchDao;
|
||||
@Autowired()
|
||||
protected ISearchResultDao mySearchResultDao;
|
||||
|
||||
private String mySecondaryPrimaryKeyParamName;
|
||||
|
||||
|
||||
@Override
|
||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
@ -117,13 +122,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
ourLog.info("Processed addTag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId, w.getMillisAndRestart() });
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome create(final T theResource) {
|
||||
return create(theResource, null, true, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome create(final T theResource, RequestDetails theRequestDetails) {
|
||||
return create(theResource, null, true, theRequestDetails);
|
||||
|
@ -175,7 +179,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> deleteConflicts, RequestDetails theRequestDetails) {
|
||||
public DaoMethodOutcome delete(IIdType theId, List<DeleteConflict> theDeleteConflicts, RequestDetails theRequestDetails) {
|
||||
if (theId == null || !theId.hasIdPart()) {
|
||||
throw new InvalidRequestException("Can not perform delete, no ID provided");
|
||||
}
|
||||
|
@ -188,7 +192,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
T resourceToDelete = toResource(myResourceType, entity, false);
|
||||
|
||||
validateOkToDelete(deleteConflicts, entity);
|
||||
validateOkToDelete(theDeleteConflicts, entity);
|
||||
|
||||
preDelete(resourceToDelete, entity);
|
||||
|
||||
|
@ -311,7 +315,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
retVal.setOperationOutcome(oo);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DeleteMethodOutcome deleteByUrl(String theUrl, RequestDetails theRequestDetails) {
|
||||
List<DeleteConflict> deleteConflicts = new ArrayList<DeleteConflict>();
|
||||
|
@ -322,7 +326,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
return outcome;
|
||||
}
|
||||
|
||||
|
||||
@PostConstruct
|
||||
public void detectSearchDaoDisabled() {
|
||||
if (mySearchDao != null && mySearchDao.isDisabled()) {
|
||||
|
@ -615,6 +619,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, IIdType theId, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
|
@ -636,7 +641,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <MT extends IBaseMetaType> MT metaGetOperation(Class<MT> theType, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
|
@ -851,7 +855,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm) {
|
||||
removeTag(theId, theTagType, theScheme, theTerm, null);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void removeTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
|
@ -885,7 +889,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
ourLog.info("Processed remove tag {}/{} on {} in {}ms", new Object[] { theScheme, theTerm, theId.getValue(), w.getMillisAndRestart() });
|
||||
}
|
||||
|
||||
|
||||
@Transactional(propagation=Propagation.SUPPORTS)
|
||||
@Override
|
||||
public IBundleProvider search(final SearchParameterMap theParams) {
|
||||
|
@ -911,6 +915,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Long> searchForIds(SearchParameterMap theParams) {
|
||||
|
||||
|
@ -929,9 +936,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Required
|
||||
public void setResourceType(Class<? extends IBaseResource> theTableType) {
|
||||
|
@ -1157,13 +1161,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
ourLog.info(msg);
|
||||
return outcome;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome update(T theResource, String theMatchUrl, RequestDetails theRequestDetails) {
|
||||
return update(theResource, theMatchUrl, true, theRequestDetails);
|
||||
|
@ -1200,7 +1204,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void validateOkToDelete(List<DeleteConflict> theDeleteConflicts, 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());
|
||||
|
@ -1210,6 +1214,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return;
|
||||
}
|
||||
|
||||
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete() == false) {
|
||||
ourLog.info("Deleting {} resource dependencies which can no longer be satisfied", resultList.size());
|
||||
myResourceLinkDao.delete(resultList);
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceLink link = resultList.get(0);
|
||||
IdDt targetId = theEntity.getIdDt();
|
||||
IdDt sourceId = link.getSourceResource().getIdDt();
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -78,11 +83,14 @@ public class DaoConfig {
|
|||
|
||||
private boolean myDeleteStaleSearches = true;
|
||||
|
||||
private boolean myEnforceReferentialIntegrityOnDelete = true;
|
||||
|
||||
private boolean myEnforceReferentialIntegrityOnWrite = true;
|
||||
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private long myExpireSearchResultsAfterMillis = DateUtils.MILLIS_PER_HOUR;
|
||||
|
||||
private int myHardTagListLimit = 1000;
|
||||
|
||||
private int myIncludeLimit = 2000;
|
||||
|
@ -90,7 +98,6 @@ public class DaoConfig {
|
|||
// update setter javadoc if default changes
|
||||
// ***
|
||||
private boolean myIndexContainedResources = true;
|
||||
|
||||
private List<IServerInterceptor> myInterceptors;
|
||||
// ***
|
||||
// update setter javadoc if default changes
|
||||
|
@ -323,6 +330,39 @@ public class DaoConfig {
|
|||
return myDefaultSearchParamsCanBeOverridden;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
|
||||
* deleted even if other resources currently contain references to them.
|
||||
* <p>
|
||||
* This property can cause confusing results for clients of the server since searches, includes,
|
||||
* and other FHIR features may not behave as expected when referential integrity is not
|
||||
* preserved. Use this feature with caution.
|
||||
* </p>
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEnforceReferentialIntegrityOnDelete() {
|
||||
return myEnforceReferentialIntegrityOnDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
|
||||
* created or updated even if they contain references to local resources that do not exist.
|
||||
* <p>
|
||||
* For example, if a patient contains a reference to managing organization <code>Organization/FOO</code>
|
||||
* but FOO is not a valid ID for an organization on the server, the operation will be blocked unless
|
||||
* this propery has been set to <code>false</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* This property can cause confusing results for clients of the server since searches, includes,
|
||||
* and other FHIR features may not behave as expected when referential integrity is not
|
||||
* preserved. Use this feature with caution.
|
||||
* </p>
|
||||
*/
|
||||
public boolean isEnforceReferentialIntegrityOnWrite() {
|
||||
return myEnforceReferentialIntegrityOnWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion
|
||||
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION.
|
||||
|
@ -356,7 +396,7 @@ public class DaoConfig {
|
|||
|
||||
/**
|
||||
* If set to {@literal true} (default is true), if a client performs an update which does not actually
|
||||
* result in any chance to a given resource (e.g. an update where the resource body matches the
|
||||
* result in any chance to a given resource (e.g. an update where the resource body matches the
|
||||
* existing resource body in the database) the operation will succeed but a new version (and corresponding history
|
||||
* entry) will not actually be created. The existing resource version will be returned to the client.
|
||||
* <p>
|
||||
|
@ -445,6 +485,37 @@ public class DaoConfig {
|
|||
myDeferIndexingForCodesystemsOfSize = theDeferIndexingForCodesystemsOfSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
|
||||
* deleted even if other resources currently contain references to them.
|
||||
* <p>
|
||||
* This property can cause confusing results for clients of the server since searches, includes,
|
||||
* and other FHIR features may not behave as expected when referential integrity is not
|
||||
* preserved. Use this feature with caution.
|
||||
* </p>
|
||||
*/
|
||||
public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) {
|
||||
myEnforceReferentialIntegrityOnDelete = theEnforceReferentialIntegrityOnDelete;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to <code>false</code> (default is <code>true</code>) resources will be permitted to be
|
||||
* created or updated even if they contain references to local resources that do not exist.
|
||||
* <p>
|
||||
* For example, if a patient contains a reference to managing organization <code>Organization/FOO</code>
|
||||
* but FOO is not a valid ID for an organization on the server, the operation will be blocked unless
|
||||
* this propery has been set to <code>false</code>
|
||||
* </p>
|
||||
* <p>
|
||||
* This property can cause confusing results for clients of the server since searches, includes,
|
||||
* and other FHIR features may not behave as expected when referential integrity is not
|
||||
* preserved. Use this feature with caution.
|
||||
* </p>
|
||||
*/
|
||||
public void setEnforceReferentialIntegrityOnWrite(boolean theEnforceReferentialIntegrityOnWrite) {
|
||||
myEnforceReferentialIntegrityOnWrite = theEnforceReferentialIntegrityOnWrite;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is set to <code>false</code> (default is <code>true</code>) the stale search deletion
|
||||
* task will be disabled (meaning that search results will be retained in the database indefinitely). USE WITH CAUTION.
|
||||
|
@ -552,7 +623,7 @@ public class DaoConfig {
|
|||
public void setMaximumSearchResultCountInTransaction(int theMaximumSearchResultCountInTransaction) {
|
||||
myMaximumSearchResultCountInTransaction = theMaximumSearchResultCountInTransaction;
|
||||
}
|
||||
|
||||
|
||||
public void setResourceEncoding(ResourceEncodingEnum theResourceEncoding) {
|
||||
myResourceEncoding = theResourceEncoding;
|
||||
}
|
||||
|
@ -602,7 +673,7 @@ public class DaoConfig {
|
|||
|
||||
/**
|
||||
* If set to {@literal true} (default is true), if a client performs an update which does not actually
|
||||
* result in any chance to a given resource (e.g. an update where the resource body matches the
|
||||
* result in any chance to a given resource (e.g. an update where the resource body matches the
|
||||
* existing resource body in the database) the operation will succeed but a new version (and corresponding history
|
||||
* entry) will not actually be created. The existing resource version will be returned to the client.
|
||||
* <p>
|
||||
|
@ -611,7 +682,7 @@ public class DaoConfig {
|
|||
*/
|
||||
public void setSuppressUpdatesWithNoChange(boolean theSuppressUpdatesWithNoChange) {
|
||||
mySuppressUpdatesWithNoChange = theSuppressUpdatesWithNoChange;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package ca.uhn.fhir.jpa.dao.data;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import ca.uhn.fhir.jpa.entity.ResourceLink;
|
||||
|
||||
public interface IResourceLinkDao extends JpaRepository<ResourceLink, Long> {
|
||||
// nothing
|
||||
}
|
|
@ -31,6 +31,7 @@ import javax.persistence.Query;
|
|||
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -67,6 +68,7 @@ import ca.uhn.fhir.rest.param.DateParam;
|
|||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
||||
|
@ -333,6 +335,21 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
throw new UnprocessableEntityException("Subscription.criteria must be in the form \"{Resource Type}?[params]\"");
|
||||
}
|
||||
|
||||
if (theResource.getChannel().getType() == null) {
|
||||
throw new UnprocessableEntityException("Subscription.channel.type must be populated");
|
||||
} else if (theResource.getChannel().getType() == SubscriptionChannelType.RESTHOOK) {
|
||||
if (isBlank(theResource.getChannel().getPayload())) {
|
||||
throw new UnprocessableEntityException("Subscription.channel.payload must be populated for rest-hook subscriptions");
|
||||
}
|
||||
|
||||
if (EncodingEnum.forContentType(theResource.getChannel().getPayload()) == null){
|
||||
throw new UnprocessableEntityException("Invalid value for Subscription.channel.payload: " + theResource.getChannel().getPayload());
|
||||
}
|
||||
if (isBlank(theResource.getChannel().getEndpoint())){
|
||||
throw new UnprocessableEntityException("Rest-hook subscriptions must have Subscription.channel.endpoint defined");
|
||||
}
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resDef;
|
||||
try {
|
||||
resDef = getContext().getResourceDefinition(resType);
|
||||
|
|
|
@ -70,10 +70,11 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookSubscriptionDstu2Interceptor.class);
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
private boolean myNotifyOnDelete = false;
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
private boolean myNotifyOnDelete = false;
|
||||
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu2")
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
|
@ -126,7 +127,7 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
|
||||
HttpUriRequest request = null;
|
||||
String resourceName = myCtx.getResourceDefinition(theResource).getName();
|
||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
||||
|
||||
String payload = theSubscription.getChannel().getPayload();
|
||||
String resourceId = theResource.getIdElement().getIdPart();
|
||||
|
@ -223,7 +224,7 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
|
||||
private String getResourceName(IBaseResource theResource) {
|
||||
return myCtx.getResourceDefinition(theResource).getName();
|
||||
return myFhirContext.getResourceDefinition(theResource).getName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +309,7 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
IIdType idType = theResource.getIdElement();
|
||||
ourLog.info("resource created type: {}", theRequest.getResourceName());
|
||||
ourLog.info("resource created type: {}", getResourceName(theResource));
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
|
@ -320,7 +321,7 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
ourLog.info("Subscription was added. Id: " + subscription.getId());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, theRequest.getResourceName(), RestOperationTypeEnum.CREATE);
|
||||
checkSubscriptions(idType, getResourceName(theResource), RestOperationTypeEnum.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,7 +380,15 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
}
|
||||
|
||||
public void setFhirContext(FhirContext theFhirContext) {
|
||||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
public void setNotifyOnDelete(boolean notifyOnDelete) {
|
||||
this.myNotifyOnDelete = notifyOnDelete;
|
||||
}
|
||||
|
||||
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
|
||||
mySubscriptionDao = theSubscriptionDao;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,16 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookSubscriptionDstu3Interceptor.class);
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
public void setFhirContext(FhirContext theFhirContext) {
|
||||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
public void setSubscriptionDao(IFhirResourceDao<Subscription> theSubscriptionDao) {
|
||||
mySubscriptionDao = theSubscriptionDao;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu3")
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
|
@ -125,7 +134,7 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
|
||||
HttpUriRequest request = null;
|
||||
String resourceName = myCtx.getResourceDefinition(theResource).getName();
|
||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
||||
|
||||
String payload = theSubscription.getChannel().getPayload();
|
||||
String resourceId = theResource.getIdElement().getIdPart();
|
||||
|
@ -215,7 +224,7 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
|
||||
private String getResourceName(IBaseResource theResource) {
|
||||
return myCtx.getResourceDefinition(theResource).getName();
|
||||
return myFhirContext.getResourceDefinition(theResource).getName();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -301,7 +310,7 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
IIdType idType = theResource.getIdElement();
|
||||
ourLog.info("resource created type: {}", theRequest.getResourceName());
|
||||
ourLog.info("resource created type: {}", getResourceName(theResource));
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
|
@ -313,7 +322,7 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
ourLog.info("Subscription was added, id: {} - Have {}", subscription.getIdElement().getIdPart(), myRestHookSubscriptions.size());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, theRequest.getResourceName(), RestOperationTypeEnum.CREATE);
|
||||
checkSubscriptions(idType, getResourceName(theResource), RestOperationTypeEnum.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
|
|
@ -42,7 +42,29 @@ import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
|||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.*;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Appointment;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ConceptMap;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Device;
|
||||
import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder;
|
||||
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Encounter;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Immunization;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Location;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Media;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Medication;
|
||||
import ca.uhn.fhir.model.dstu2.resource.MedicationAdministration;
|
||||
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Practitioner;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Questionnaire;
|
||||
import ca.uhn.fhir.model.dstu2.resource.QuestionnaireResponse;
|
||||
import ca.uhn.fhir.model.dstu2.resource.StructureDefinition;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Substance;
|
||||
import ca.uhn.fhir.model.dstu2.resource.ValueSet;
|
||||
import ca.uhn.fhir.parser.IParser;
|
||||
import ca.uhn.fhir.rest.method.MethodUtil;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Organization;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class FhirResourceDaoDstu3ReferentialIntegrityTest extends BaseJpaDstu3Test {
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterResetConfig() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnWrite(new DaoConfig().isEnforceReferentialIntegrityOnWrite());
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnDelete(new DaoConfig().isEnforceReferentialIntegrityOnDelete());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUnknownReferenceFail() throws Exception {
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setManagingOrganization(new Reference("Organization/AAA"));
|
||||
try {
|
||||
myPatientDao.create(p);
|
||||
fail();
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals("Resource Organization/AAA not found, specified in path: Patient.managingOrganization", e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateUnknownReferenceAllow() throws Exception {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnWrite(false);
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setManagingOrganization(new Reference("Organization/AAA"));
|
||||
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
p = myPatientDao.read(id);
|
||||
assertEquals("Organization/AAA", p.getManagingOrganization().getReference());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteFail() throws Exception {
|
||||
Organization o = new Organization();
|
||||
o.setName("FOO");
|
||||
IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless();
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setManagingOrganization(new Reference(oid));
|
||||
IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
try {
|
||||
myOrganizationDao.delete(oid);
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
assertEquals("Unable to delete Organization/"+oid.getIdPart()+" because at least one resource has a reference to this resource. First reference found was resource Organization/"+oid.getIdPart()+" in path Patient.managingOrganization", e.getMessage());
|
||||
}
|
||||
|
||||
myPatientDao.delete(pid);
|
||||
myOrganizationDao.delete(oid);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAllow() throws Exception {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnDelete(false);
|
||||
|
||||
Organization o = new Organization();
|
||||
o.setName("FOO");
|
||||
IIdType oid = myOrganizationDao.create(o).getId().toUnqualifiedVersionless();
|
||||
|
||||
Patient p = new Patient();
|
||||
p.setManagingOrganization(new Reference(oid));
|
||||
IIdType pid = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
|
||||
myOrganizationDao.delete(oid);
|
||||
myPatientDao.delete(pid);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
|||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionFlaggedResourceDataDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
@ -170,6 +171,7 @@ public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test {
|
|||
public void testCreateSubscriptionInvalidCriteria() {
|
||||
Subscription subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subs.setCriteria("Observation");
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
|
@ -180,6 +182,7 @@ public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test {
|
|||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subs.setCriteria("http://foo.com/Observation?AAA=BBB");
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
|
@ -190,6 +193,7 @@ public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test {
|
|||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subs.setCriteria("ObservationZZZZ?a=b");
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
|
@ -205,15 +209,58 @@ public class FhirResourceDaoDstu3SubscriptionTest extends BaseJpaDstu3Test {
|
|||
mySubscriptionDao.create(subs, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated on this server"));
|
||||
assertThat(e.getMessage(), containsString("Subscription.channel.type must be populated"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Subscription.channel.payload must be populated for rest-hook subscriptions"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
|
||||
subs.getChannel().setPayload("text/html");
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Invalid value for Subscription.channel.payload: text/html"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
|
||||
subs.getChannel().setPayload("application/fhir+xml");
|
||||
try {
|
||||
mySubscriptionDao.create(subs, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertThat(e.getMessage(), containsString("Rest-hook subscriptions must have Subscription.channel.endpoint defined"));
|
||||
}
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
|
||||
|
||||
subs = new Subscription();
|
||||
subs.setStatus(SubscriptionStatus.REQUESTED);
|
||||
subs.setCriteria("Observation?identifier=123");
|
||||
subs.getChannel().setType(SubscriptionChannelType.RESTHOOK);
|
||||
subs.getChannel().setPayload("application/fhir+json");
|
||||
subs.getChannel().setEndpoint("http://localhost:8080");
|
||||
assertTrue(mySubscriptionDao.create(subs, mySrd).getId().hasIdPart());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -24,7 +24,6 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
|||
import ca.uhn.fhir.jpa.rp.dstu.ObservationResourceProvider;
|
||||
import ca.uhn.fhir.jpa.rp.dstu.OrganizationResourceProvider;
|
||||
import ca.uhn.fhir.jpa.rp.dstu.PatientResourceProvider;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.api.BundleEntry;
|
||||
import ca.uhn.fhir.model.dstu.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||
|
@ -32,6 +31,7 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
|
|||
import ca.uhn.fhir.model.dstu.resource.Questionnaire;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public class SystemProviderDstu1Test extends BaseJpaTest {
|
||||
|
@ -100,7 +100,7 @@ public class SystemProviderDstu1Test extends BaseJpaTest {
|
|||
JpaSystemProviderDstu1 systemProv = ourAppCtx.getBean(JpaSystemProviderDstu1.class, "mySystemProviderDstu1");
|
||||
restServer.setPlainProviders(systemProv);
|
||||
|
||||
int myPort = RandomServerPortProvider.findFreePort();
|
||||
int myPort = PortUtil.findFreePort();
|
||||
ourServer = new Server(myPort);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||
|
|
|
@ -34,7 +34,6 @@ import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
|
|||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
|
@ -43,6 +42,7 @@ import ca.uhn.fhir.rest.client.ServerValidationModeEnum;
|
|||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
|
||||
public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
||||
|
@ -77,7 +77,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
myFhirCtx.setParserErrorHandler(new StrictErrorHandler());
|
||||
|
||||
if (ourServer == null) {
|
||||
ourPort = RandomServerPortProvider.findFreePort();
|
||||
ourPort = PortUtil.findFreePort();
|
||||
|
||||
ourRestServer = new RestfulServer(myFhirCtx);
|
||||
|
||||
|
|
|
@ -9,25 +9,34 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.*;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription.Channel;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.*;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -250,7 +259,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
ourListenerPort = RandomServerPortProvider.findFreePort();
|
||||
ourListenerPort = PortUtil.findFreePort();
|
||||
ourListenerRestServer = new RestfulServer(FhirContext.forDstu2());
|
||||
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
|
||||
|
||||
|
|
|
@ -3,8 +3,11 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
|
@ -21,6 +24,7 @@ import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
|||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
||||
|
@ -29,12 +33,14 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
|||
*/
|
||||
public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static List<String> ourContentTypes = new ArrayList<String>();
|
||||
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
|
||||
private static int ourListenerPort;
|
||||
private static RestfulServer ourListenerRestServer;
|
||||
private static Server ourListenerServer;
|
||||
private static String ourListenerServerBase;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestDstu3Test.class);
|
||||
|
||||
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
|
||||
|
||||
@After
|
||||
|
@ -57,18 +63,19 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
public void beforeReset() {
|
||||
ourCreatedObservations.clear();
|
||||
ourUpdatedObservations.clear();
|
||||
ourContentTypes.clear();
|
||||
}
|
||||
|
||||
private Subscription createSubscription(String criteria, String payload, String endpoint) {
|
||||
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
subscription.setCriteria(criteria);
|
||||
subscription.setCriteria(theCriteria);
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
channel.setPayload(payload);
|
||||
channel.setEndpoint(endpoint);
|
||||
channel.setPayload(thePayload);
|
||||
channel.setEndpoint(theEndpoint);
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
|
@ -94,9 +101,29 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
|
||||
return observation;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionJson() throws Exception {
|
||||
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
|
||||
String payload = "application/fhir+json";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
||||
|
||||
createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see 1 subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionApplicationJson() throws Exception {
|
||||
String payload = "application/json";
|
||||
|
||||
String code = "1000000050";
|
||||
|
@ -112,6 +139,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
|
||||
|
||||
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
|
||||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
@ -169,7 +197,28 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionXml() throws Exception {
|
||||
public void testRestHookSubscriptionApplicationXmlJson() throws Exception {
|
||||
String payload = "application/fhir+xml";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
||||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see 1 subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionApplicationXml() throws Exception {
|
||||
String payload = "application/xml";
|
||||
|
||||
String code = "1000000050";
|
||||
|
@ -185,6 +234,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
|
||||
|
||||
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
|
||||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
@ -240,7 +290,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
Assert.assertFalse(observation1.getId().isEmpty());
|
||||
Assert.assertFalse(observation2.getId().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
ourListenerPort = RandomServerPortProvider.findFreePort();
|
||||
|
@ -271,8 +321,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
public static class ObservationListener implements IResourceProvider {
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam Observation theObservation) {
|
||||
public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
|
||||
ourLog.info("Received Listener Create");
|
||||
ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
|
||||
ourCreatedObservations.add(theObservation);
|
||||
return new MethodOutcome(new IdType("Observation/1"), true);
|
||||
}
|
||||
|
@ -283,9 +334,10 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
|
||||
@Update
|
||||
public MethodOutcome update(@ResourceParam Observation theObservation) {
|
||||
public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
|
||||
ourLog.info("Received Listener Update");
|
||||
ourUpdatedObservations.add(theObservation);
|
||||
ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
|
||||
return new MethodOutcome(new IdType("Observation/1"), false);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,309 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription.Channel;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ObservationStatusEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
|
||||
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
|
||||
private static int ourListenerPort;
|
||||
private static RestfulServer ourListenerRestServer;
|
||||
private static Server ourListenerServer;
|
||||
private static String ourListenerServerBase;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.class);
|
||||
private static List<Observation> ourUpdatedObservations = Lists.newArrayList();
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
myDaoConfig.setAllowMultipleDelete(true);
|
||||
ourLog.info("Deleting all subscriptions");
|
||||
ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute();
|
||||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeReset() {
|
||||
ourCreatedObservations.clear();
|
||||
ourUpdatedObservations.clear();
|
||||
}
|
||||
|
||||
private Subscription createSubscription(String criteria, String payload, String endpoint) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Channel channel = new Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
channel.setPayload(payload);
|
||||
channel.setEndpoint(endpoint);
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
subscription.setId(methodOutcome.getId().getIdPart());
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
private Observation sendObservation(String code, String system) {
|
||||
Observation observation = new Observation();
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode(code);
|
||||
coding.setSystem(system);
|
||||
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
|
||||
|
||||
String observationId = methodOutcome.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionJson() throws Exception {
|
||||
String payload = "application/json";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
||||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see 1 subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
|
||||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
||||
subscriptionTemp.setCriteria(criteria1);
|
||||
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
|
||||
|
||||
|
||||
Observation observation2 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see two subscription notifications
|
||||
Thread.sleep(500);
|
||||
assertEquals(3, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
|
||||
|
||||
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see only one subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation3.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode(code + "111");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
|
||||
|
||||
// Should see no subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId());
|
||||
|
||||
CodeableConceptDt codeableConcept1 = new CodeableConceptDt();
|
||||
observation3a.setCode(codeableConcept1);
|
||||
CodingDt coding1 = codeableConcept1.addCoding();
|
||||
coding1.setCode(code);
|
||||
coding1.setSystem("SNOMED-CT");
|
||||
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
|
||||
|
||||
// Should see only one subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(1, ourUpdatedObservations.size());
|
||||
|
||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||
Assert.assertFalse(observation1.getId().isEmpty());
|
||||
Assert.assertFalse(observation2.getId().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionXml() throws Exception {
|
||||
String payload = "application/xml";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
|
||||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see 1 subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(1, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
|
||||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
||||
subscriptionTemp.setCriteria(criteria1);
|
||||
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
|
||||
|
||||
|
||||
Observation observation2 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see two subscription notifications
|
||||
Thread.sleep(500);
|
||||
assertEquals(3, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
ourClient.delete().resourceById(new IdDt("Subscription/"+ subscription2.getId())).execute();
|
||||
|
||||
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see only one subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation3.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode(code + "111");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
|
||||
|
||||
// Should see no subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(0, ourUpdatedObservations.size());
|
||||
|
||||
Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId());
|
||||
|
||||
CodeableConceptDt codeableConcept1 = new CodeableConceptDt();
|
||||
observation3a.setCode(codeableConcept1);
|
||||
CodingDt coding1 = codeableConcept1.addCoding();
|
||||
coding1.setCode(code);
|
||||
coding1.setSystem("SNOMED-CT");
|
||||
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
|
||||
|
||||
// Should see only one subscription notification
|
||||
Thread.sleep(500);
|
||||
assertEquals(4, ourCreatedObservations.size());
|
||||
assertEquals(1, ourUpdatedObservations.size());
|
||||
|
||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||
Assert.assertFalse(observation1.getId().isEmpty());
|
||||
Assert.assertFalse(observation2.getId().isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void startListenerServer() throws Exception {
|
||||
ourListenerPort = PortUtil.findFreePort();
|
||||
ourListenerRestServer = new RestfulServer(FhirContext.forDstu2());
|
||||
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
|
||||
|
||||
ObservationListener obsListener = new ObservationListener();
|
||||
ourListenerRestServer.setResourceProviders(obsListener);
|
||||
|
||||
ourListenerServer = new Server(ourListenerPort);
|
||||
|
||||
ServletContextHandler proxyHandler = new ServletContextHandler();
|
||||
proxyHandler.setContextPath("/");
|
||||
|
||||
ServletHolder servletHolder = new ServletHolder();
|
||||
servletHolder.setServlet(ourListenerRestServer);
|
||||
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
|
||||
|
||||
ourListenerServer.setHandler(proxyHandler);
|
||||
ourListenerServer.start();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void stopListenerServer() throws Exception {
|
||||
ourListenerServer.stop();
|
||||
}
|
||||
|
||||
public static class ObservationListener implements IResourceProvider {
|
||||
|
||||
@Create
|
||||
public MethodOutcome create(@ResourceParam Observation theObservation) {
|
||||
ourLog.info("Received Listener Create");
|
||||
ourCreatedObservations.add(theObservation);
|
||||
return new MethodOutcome(new IdDt("Observation/1"), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends IBaseResource> getResourceType() {
|
||||
return Observation.class;
|
||||
}
|
||||
|
||||
@Update
|
||||
public MethodOutcome update(@ResourceParam Observation theObservation) {
|
||||
ourLog.info("Received Listener Update");
|
||||
ourUpdatedObservations.add(theObservation);
|
||||
return new MethodOutcome(new IdDt("Observation/1"), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -125,6 +125,14 @@
|
|||
Fix XhtmlParser to correctly handle hexadecimal escaped literals. Thanks to
|
||||
Gijsbert van den Brink for the Pull Request!
|
||||
</action>
|
||||
<action type="add">
|
||||
JPA server now has configurable properties that allow referential integrity
|
||||
to be disabled for both writes and deletes. This is useful in some cases
|
||||
where data integrity is not wanted or not possible. It can also be useful
|
||||
if you want to delete large amounts of interconnected data quickly.
|
||||
<![CDATA[<br/><br/>]]>
|
||||
A corresponding flag has been added to the CLI tool as well.
|
||||
</action>
|
||||
</release>
|
||||
<release version="2.4" date="2017-04-19">
|
||||
<action type="add">
|
||||
|
|
Loading…
Reference in New Issue