Updates for #646
Squashed commit of the following: commit 9d6d5e92dabb75c4eb185b061d20f487b8856795 Author: James <jamesagnew@gmail.com> Date: Sun May 21 15:27:26 2017 -0400 Get subscriptions working commit 6bccdd7594f0a4f802212e11cc823f7a92dd9a5c Author: James Agnew <jamesagnew@gmail.com> Date: Sun May 21 14:37:40 2017 -0400 Work on subscription commit 129f4c9d0d1e6c8fa56dbc5cf78a34c1d6659705 Author: James <jamesagnew@gmail.com> Date: Sat May 20 19:58:11 2017 -0400 Work on merging subscription
This commit is contained in:
parent
11eeeedf0d
commit
a13247ad4b
|
@ -14,6 +14,7 @@
|
|||
<name>HAPI FHIR JPA Server</name>
|
||||
|
||||
<dependencies>
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
|
@ -35,6 +36,7 @@
|
|||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>9.5.1-5</version>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@ -264,11 +266,21 @@
|
|||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-entitymanager</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.jboss.spec.javax.transaction</groupId>
|
||||
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
|
|
|
@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.config;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.*;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu2;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
|
@ -118,4 +120,10 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
return new HapiTerminologySvcDstu2();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionDstu2Interceptor restHookSubscriptionDstu3Interceptor() {
|
||||
return new RestHookSubscriptionDstu2Interceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
|||
import ca.uhn.fhir.jpa.dao.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.term.HapiTerminologySvcDstu3;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologyLoaderSvc;
|
||||
|
@ -132,4 +133,10 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
return new JpaValidationSupportChainDstu3();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public RestHookSubscriptionDstu3Interceptor restHookSubscriptionDstu3Interceptor() {
|
||||
return new RestHookSubscriptionDstu3Interceptor();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.config.dstu3;
|
|||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
@ -32,7 +33,9 @@ import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
|||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
||||
|
||||
import ca.uhn.fhir.jpa.interceptor.WebSocketSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketHandlerDstu3;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSocket()
|
||||
|
@ -59,7 +62,6 @@ public class WebsocketDstu3Config implements WebSocketConfigurer {
|
|||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
getScheduledThreadPoolExecutor().setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
// getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true);
|
||||
getScheduledThreadPoolExecutor().setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
|
||||
}
|
||||
};
|
||||
|
@ -69,4 +71,10 @@ public class WebsocketDstu3Config implements WebSocketConfigurer {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IServerInterceptor webSocketSubscriptionDstu3Interceptor(){
|
||||
return new WebSocketSubscriptionDstu3Interceptor();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -876,7 +876,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
if (changed == false) {
|
||||
if (theEntity.getResource() == null) {
|
||||
changed = true;
|
||||
}else {
|
||||
} else {
|
||||
changed = !Arrays.equals(theEntity.getResource(), bytes);
|
||||
}
|
||||
}
|
||||
|
@ -1164,13 +1164,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
return false;
|
||||
}
|
||||
|
||||
// protected ResourceTable toEntity(IResource theResource) {
|
||||
// ResourceTable retVal = new ResourceTable();
|
||||
//
|
||||
// populateResourceIntoEntity(theResource, retVal, true);
|
||||
//
|
||||
// return retVal;
|
||||
// }
|
||||
// protected ResourceTable toEntity(IResource theResource) {
|
||||
// ResourceTable retVal = new ResourceTable();
|
||||
//
|
||||
// populateResourceIntoEntity(theResource, retVal, true);
|
||||
//
|
||||
// return retVal;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) {
|
||||
|
|
|
@ -118,6 +118,7 @@ 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);
|
||||
|
@ -415,23 +416,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return outcome;
|
||||
}
|
||||
|
||||
private void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
|
||||
IIdType idType = theResourceId;
|
||||
String newVersion;
|
||||
long newVersionLong;
|
||||
if (idType == null || idType.getVersionIdPart() == null) {
|
||||
newVersion = "1";
|
||||
newVersionLong = 1;
|
||||
} else {
|
||||
newVersionLong = idType.getVersionIdPartAsLong() + 1;
|
||||
newVersion = Long.toString(newVersionLong);
|
||||
}
|
||||
|
||||
IIdType newId = theResourceId.withVersion(newVersion);
|
||||
theResource.getIdElement().setValue(newId.getValue());
|
||||
theSavedEntity.setVersion(newVersionLong);
|
||||
}
|
||||
|
||||
private <MT extends IBaseMetaType> void doMetaAdd(MT theMetaAdd, BaseHasResource entity) {
|
||||
List<TagDefinition> tags = toTagList(theMetaAdd);
|
||||
|
||||
|
@ -548,6 +532,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
private void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
|
||||
IIdType idType = theResourceId;
|
||||
String newVersion;
|
||||
long newVersionLong;
|
||||
if (idType == null || idType.getVersionIdPart() == null) {
|
||||
newVersion = "1";
|
||||
newVersionLong = 1;
|
||||
} else {
|
||||
newVersionLong = idType.getVersionIdPartAsLong() + 1;
|
||||
newVersion = Long.toString(newVersionLong);
|
||||
}
|
||||
|
||||
IIdType newId = theResourceId.withVersion(newVersion);
|
||||
theResource.getIdElement().setValue(newId.getValue());
|
||||
theSavedEntity.setVersion(newVersionLong);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MT extends IBaseMetaType> MT metaAddOperation(IIdType theResourceId, MT theMetaAdd, RequestDetails theRequestDetails) {
|
||||
// Notify interceptors
|
||||
|
@ -614,7 +615,6 @@ 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,6 +636,7 @@ 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
|
||||
|
@ -910,9 +911,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public Set<Long> searchForIds(SearchParameterMap theParams) {
|
||||
|
||||
|
@ -931,6 +929,9 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Required
|
||||
public void setResourceType(Class<? extends IBaseResource> theTableType) {
|
||||
|
@ -1060,12 +1061,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
return update(theResource, theMatchUrl, null);
|
||||
}
|
||||
|
||||
@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, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails) {
|
||||
StopWatch w = new StopWatch();
|
||||
|
@ -1163,11 +1158,37 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource definition from the criteria which specifies the resource type
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
String resourceName;
|
||||
if(criteria == null || criteria.trim().isEmpty()){
|
||||
throw new IllegalArgumentException("Criteria cannot be empty");
|
||||
}
|
||||
if(criteria.contains("?")){
|
||||
resourceName = criteria.substring(0, criteria.indexOf("?"));
|
||||
}else{
|
||||
resourceName = criteria;
|
||||
}
|
||||
|
||||
return getContext().getResourceDefinition(resourceName);
|
||||
}
|
||||
|
||||
private void validateGivenIdIsAppropriateToRetrieveResource(IIdType theId, BaseHasResource entity) {
|
||||
if (entity.getForcedId() != null) {
|
||||
if (theId.isIdPartValidLong()) {
|
||||
|
|
|
@ -41,11 +41,6 @@ import ca.uhn.fhir.util.FhirTerser;
|
|||
|
||||
public class FhirResourceDaoDstu1<T extends IResource> extends BaseHapiFhirResourceDao<T> {
|
||||
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> getIncludeValues(FhirTerser t, Include next, IBaseResource nextResource, RuntimeResourceDefinition def) {
|
||||
List<Object> values;
|
||||
|
|
|
@ -145,11 +145,6 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class IdChecker implements IValidatorModule {
|
||||
|
||||
private ValidationModeEnum myMode;
|
||||
|
|
|
@ -171,7 +171,6 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
|||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
|
||||
criteriaUrl = new SearchParameterMap();
|
||||
long start = theSubscriptionTable.getMostRecentMatch().getTime();
|
||||
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
|
||||
if (end <= start) {
|
||||
|
|
|
@ -167,26 +167,6 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resource definition from the criteria which specifies the resource type
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
String resourceName;
|
||||
if(criteria == null || criteria.trim().isEmpty()){
|
||||
throw new IllegalArgumentException("Criteria cannot be empty");
|
||||
}
|
||||
if(criteria.contains("?")){
|
||||
resourceName = criteria.substring(0, criteria.indexOf("?"));
|
||||
}else{
|
||||
resourceName = criteria;
|
||||
}
|
||||
|
||||
return fhirContext.getResourceDefinition(resourceName);
|
||||
}
|
||||
|
||||
private class IdChecker implements IValidatorModule {
|
||||
|
||||
private ValidationModeEnum myMode;
|
||||
|
|
|
@ -57,7 +57,6 @@ import ca.uhn.fhir.jpa.dao.data.ISubscriptionTableDao;
|
|||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionFlaggedResource;
|
||||
import ca.uhn.fhir.jpa.entity.SubscriptionTable;
|
||||
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
|
@ -66,11 +65,12 @@ import ca.uhn.fhir.rest.api.SortOrderEnum;
|
|||
import ca.uhn.fhir.rest.api.SortSpec;
|
||||
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.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
|
||||
public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subscription>implements IFhirResourceDaoSubscription<Subscription> {
|
||||
public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subscription> implements IFhirResourceDaoSubscription<Subscription> {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoSubscriptionDstu3.class);
|
||||
|
||||
|
@ -159,7 +159,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET){
|
||||
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET) {
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
@ -172,8 +172,8 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
criteriaUrl.setLoadSynchronousUpTo(1000);
|
||||
|
||||
criteriaUrl = new SearchParameterMap();
|
||||
long start = theSubscriptionTable.getMostRecentMatch().getTime();
|
||||
long end = System.currentTimeMillis() - getConfig().getSubscriptionPollDelay();
|
||||
if (end <= start) {
|
||||
|
@ -181,11 +181,11 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
return 0;
|
||||
}
|
||||
|
||||
ourLog.debug("Subscription {} search from {} to {}", new Object[] { subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end)) });
|
||||
ourLog.info("Subscription {} search from {} to {}", new Object[] { subscription.getIdElement().getIdPart(), new InstantDt(new Date(start)), new InstantDt(new Date(end)) });
|
||||
|
||||
DateRangeParam range = new DateRangeParam();
|
||||
range.setLowerBound(new DateParam(QuantityCompararatorEnum.GREATERTHAN, start));
|
||||
range.setUpperBound(new DateParam(QuantityCompararatorEnum.LESSTHAN, end));
|
||||
range.setLowerBound(new DateParam(ParamPrefixEnum.GREATERTHAN, start));
|
||||
range.setUpperBound(new DateParam(ParamPrefixEnum.LESSTHAN, end));
|
||||
criteriaUrl.setLastUpdated(range);
|
||||
criteriaUrl.setSort(new SortSpec(Constants.PARAM_LASTUPDATED, SortOrderEnum.ASC));
|
||||
IFhirResourceDao<? extends IBaseResource> dao = getDao(resourceDef.getImplementingClass());
|
||||
|
@ -199,7 +199,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
|||
List<SubscriptionFlaggedResource> flags = new ArrayList<SubscriptionFlaggedResource>();
|
||||
Date mostRecentMatch = null;
|
||||
for (IBaseResource nextBase : results.getResources(0, results.size())) {
|
||||
IAnyResource next = (IAnyResource)nextBase;
|
||||
IAnyResource next = (IAnyResource) nextBase;
|
||||
|
||||
Date updated = next.getMeta().getLastUpdated();
|
||||
if (mostRecentMatch == null) {
|
||||
|
|
|
@ -18,34 +18,16 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.interceptor;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.FhirResourceDaoSubscriptionDstu2;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.thread.HttpRequestDstu2Job;
|
||||
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
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.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -55,271 +37,148 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.service.TMinusService;
|
||||
import ca.uhn.fhir.jpa.thread.HttpRequestDstu2Job;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
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.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
|
||||
/**
|
||||
* Adds the capability for DSTU2 rest-hook subscriptions. It is compatible with
|
||||
* DSTU2 websocket subscriptions, but not with DSTU3 subscriptions
|
||||
*/
|
||||
public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter implements IServerOperationInterceptor {
|
||||
|
||||
private static volatile ExecutorService executor;
|
||||
private final static int MAX_THREADS = 1;
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookSubscriptionDstu2Interceptor.class);
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
private boolean myNotifyOnDelete = false;
|
||||
|
||||
private final List<Subscription> myRestHookSubscriptions = new ArrayList<Subscription>();
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu2")
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
|
||||
private static volatile ExecutorService executor;
|
||||
private FhirResourceDaoSubscriptionDstu2 myResourceSubscriptionDao;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RestHookSubscriptionDstu2Interceptor.class);
|
||||
private final List<Subscription> restHookSubscriptions = new ArrayList<Subscription>();
|
||||
private boolean notifyOnDelete = false;
|
||||
|
||||
private final static int MAX_THREADS = 1;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
try {
|
||||
executor = Executors.newFixedThreadPool(MAX_THREADS);
|
||||
myResourceSubscriptionDao = SpringObjectCaster.getTargetObject(mySubscriptionDao, FhirResourceDaoSubscriptionDstu2.class);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to get DAO from PROXY");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
public void initSubscriptions() {
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_TYPE, new TokenParam(null, SubscriptionChannelTypeEnum.REST_HOOK.getCode()));
|
||||
map.add(Subscription.SP_STATUS, new TokenParam(null, SubscriptionStatusEnum.ACTIVE.getCode()));
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
for (IBaseResource resource : resourceList) {
|
||||
restHookSubscriptions.add((Subscription) resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming resources. If the resource is a rest-hook subscription, it adds
|
||||
* it to the rest-hook subscription list. Otherwise it checks to see if the resource
|
||||
* matches any rest-hook subscriptions.
|
||||
*
|
||||
* @param theDetails The request details
|
||||
* @param theResourceTable The actual created entity
|
||||
*/
|
||||
@Override
|
||||
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
String resourceType = theDetails.getResourceType();
|
||||
IIdType idType = theDetails.getId();
|
||||
logger.info("resource created type: " + resourceType);
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
Subscription subscription = (Subscription) theDetails.getResource();
|
||||
if (subscription.getChannel() != null
|
||||
&& subscription.getChannel().getType().equals(SubscriptionChannelTypeEnum.REST_HOOK.getCode())
|
||||
&& subscription.getStatus().equals(SubscriptionStatusEnum.REQUESTED.getCode())) {
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
mySubscriptionDao.update(subscription);
|
||||
restHookSubscriptions.add(subscription);
|
||||
logger.info("Subscription was added. Id: " + subscription.getId());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates to subscriptions or if an update to a resource matches
|
||||
* a rest-hook subscription
|
||||
*
|
||||
* @param theDetails The request details
|
||||
* @param theResourceTable The actual updated entity
|
||||
*/
|
||||
@Override
|
||||
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
String resourceType = theDetails.getResourceType();
|
||||
IIdType idType = theDetails.getId();
|
||||
|
||||
logger.info("resource updated type: " + resourceType);
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
Subscription subscription = (Subscription) theDetails.getResource();
|
||||
if (subscription.getChannel() != null && subscription.getChannel().getType().equals(SubscriptionChannelTypeEnum.REST_HOOK.getCode())) {
|
||||
removeLocalSubscription(subscription.getId().getIdPart());
|
||||
|
||||
if (subscription.getStatus().equals(SubscriptionStatusEnum.ACTIVE.getCode())) {
|
||||
restHookSubscriptions.add(subscription);
|
||||
logger.info("Subscription was updated. Id: " + subscription.getId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check subscriptions to see if there is a matching subscription when there is delete
|
||||
*
|
||||
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theRequest The incoming request
|
||||
* @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||
* to indicate that the server itself should not also provide a response.
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
@Override
|
||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
if (theRequestDetails.getRestOperationType().equals(RestOperationTypeEnum.DELETE)) {
|
||||
String resourceType = theRequestDetails.getResourceName();
|
||||
IIdType idType = theRequestDetails.getId();
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
String id = idType.getIdPart();
|
||||
removeLocalSubscription(id);
|
||||
} else {
|
||||
if (notifyOnDelete) {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check subscriptions and send notifications or payload
|
||||
*
|
||||
* @param idType
|
||||
* @param resourceType
|
||||
* @param theOperation
|
||||
*/
|
||||
private void checkSubscriptions(IIdType idType, String resourceType) {
|
||||
for (Subscription subscription : restHookSubscriptions) {
|
||||
//see if the criteria matches the created object
|
||||
logger.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
private void checkSubscriptions(IIdType idType, String resourceType, RestOperationTypeEnum theOperation) {
|
||||
for (Subscription subscription : myRestHookSubscriptions) {
|
||||
// see if the criteria matches the created object
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
logger.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
continue;
|
||||
}
|
||||
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
|
||||
// run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
|
||||
String criteria = subscription.getCriteria();
|
||||
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
|
||||
criteria = TMinusService.parseCriteria(criteria);
|
||||
|
||||
IBundleProvider results = getBundleProvider(criteria);
|
||||
|
||||
if (results.size() == 0) {
|
||||
continue;
|
||||
}
|
||||
Observation aa;
|
||||
//should just be one resource as it was filtered by the id
|
||||
|
||||
// should just be one resource as it was filtered by the id
|
||||
for (IBaseResource nextBase : results.getResources(0, results.size())) {
|
||||
IResource next = (IResource) nextBase;
|
||||
logger.info("Found match: queueing rest-hook notification for resource: {}", next.getIdElement());
|
||||
HttpUriRequest request = createRequest(subscription, next);
|
||||
ourLog.info("Found match: queueing rest-hook notification for resource: {}", next.getIdElement());
|
||||
HttpUriRequest request = createRequest(subscription, next, theOperation);
|
||||
if (request != null) {
|
||||
executor.submit(new HttpRequestDstu2Job(request, subscription));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTTP Post for a subscription
|
||||
*
|
||||
* @param subscription
|
||||
* @param resource
|
||||
* @return
|
||||
*/
|
||||
private HttpUriRequest createRequest(Subscription subscription, IResource resource) {
|
||||
String url = subscription.getChannel().getEndpoint();
|
||||
private HttpUriRequest createRequest(Subscription theSubscription, IResource theResource, RestOperationTypeEnum theOperation) {
|
||||
String url = theSubscription.getChannel().getEndpoint();
|
||||
while (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
|
||||
HttpUriRequest request = null;
|
||||
String payload = subscription.getChannel().getPayload();
|
||||
//HTTP post
|
||||
if (payload == null || payload.trim().length() == 0) {
|
||||
//return an empty response as there is no payload
|
||||
logger.info("No payload found, returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
//HTTP put
|
||||
else if (payload.equals("application/xml") || payload.equals("application/fhir+xml")) {
|
||||
logger.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
String resourceName = myCtx.getResourceDefinition(theResource).getName();
|
||||
|
||||
String payload = theSubscription.getChannel().getPayload();
|
||||
String resourceId = theResource.getIdElement().getIdPart();
|
||||
|
||||
// HTTP put
|
||||
if (theOperation == RestOperationTypeEnum.UPDATE && EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, theResource);
|
||||
HttpPut putRequest = new HttpPut(url + "/" + resourceName + "/" + resourceId);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_XML);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
//HTTP put
|
||||
else if (payload.equals("application/json") || payload.equals("application/fhir+json")) {
|
||||
logger.info("JSON payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
// HTTP put
|
||||
else if (theOperation == RestOperationTypeEnum.UPDATE && EncodingEnum.JSON.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("JSON payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, theResource);
|
||||
HttpPut putRequest = new HttpPut(url + "/" + resourceName + "/" + resourceId);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
//HTTP post
|
||||
else if (payload.startsWith("application/fhir+query/")) { //custom payload that is a FHIR query
|
||||
logger.info("Custom query payload found");
|
||||
String responseCriteria = subscription.getChannel().getPayload().substring(23);
|
||||
//get the encoding type from payload which is a FHIR query with &_format=
|
||||
EncodingEnum encoding = getEncoding(responseCriteria);
|
||||
IBundleProvider responseResults = getBundleProvider(responseCriteria);
|
||||
if (responseResults.size() != 0) {
|
||||
List<IBaseResource> resourcelist = responseResults.getResources(0, responseResults.size());
|
||||
Bundle bundle = createBundle(resourcelist);
|
||||
StringEntity bundleEntity = getStringEntity(encoding, bundle);
|
||||
HttpPost postRequest = new HttpPost(url);
|
||||
postRequest.setEntity(bundleEntity);
|
||||
// HTTP POST
|
||||
else if (theOperation == RestOperationTypeEnum.CREATE && EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("XML payload found");
|
||||
|
||||
request = postRequest;
|
||||
} else {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setTotal(0);
|
||||
StringEntity bundleEntity = getStringEntity(encoding, bundle);
|
||||
HttpPost postRequest = new HttpPost(url);
|
||||
postRequest.setEntity(bundleEntity);
|
||||
IdDt id = theResource.getId();
|
||||
theResource.setId(new IdDt());
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, theResource);
|
||||
theResource.setId(id);
|
||||
HttpPost putRequest = new HttpPost(url + "/" + resourceName);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_XML);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = postRequest;
|
||||
request = putRequest;
|
||||
}
|
||||
// HTTP POST
|
||||
else if (theOperation == RestOperationTypeEnum.CREATE && EncodingEnum.JSON.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("JSON payload found");
|
||||
IdDt id = theResource.getId();
|
||||
theResource.setId(new IdDt());
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, theResource);
|
||||
theResource.setId(id);
|
||||
HttpPost putRequest = new HttpPost(url + "/" + resourceName);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.warn("Unsupported payload " + payload + ". Returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
|
||||
//request.addHeader("User-Agent", USER_AGENT);
|
||||
// request.addHeader("User-Agent", USER_AGENT);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encoding from the criteria or return JSON encoding if its not found
|
||||
*
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
private EncodingEnum getEncoding(String criteria) {
|
||||
//check criteria
|
||||
String params = criteria.substring(criteria.indexOf('?') + 1);
|
||||
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
|
||||
for (NameValuePair nameValuePair : paramValues) {
|
||||
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
|
||||
return EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
}
|
||||
}
|
||||
return EncodingEnum.JSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search based on a query criteria
|
||||
*
|
||||
|
@ -327,35 +186,36 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
* @return
|
||||
*/
|
||||
private IBundleProvider getBundleProvider(String criteria) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
RuntimeResourceDefinition responseResourceDef = myResourceSubscriptionDao.validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(myResourceSubscriptionDao, myResourceSubscriptionDao.getContext(), criteria, responseResourceDef);
|
||||
RuntimeResourceDefinition responseResourceDef = mySubscriptionDao.validateCriteriaAndReturnResourceDefinition(criteria);
|
||||
SearchParameterMap responseCriteriaUrl = BaseHapiFhirDao.translateMatchUrl(mySubscriptionDao, mySubscriptionDao.getContext(), criteria, responseResourceDef);
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IFhirResourceDao<? extends IBaseResource> responseDao = myResourceSubscriptionDao.getDao(responseResourceDef.getImplementingClass());
|
||||
IFhirResourceDao<? extends IBaseResource> responseDao = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
|
||||
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
|
||||
return responseResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bundle to return to the client
|
||||
* Get subscription from cache
|
||||
*
|
||||
* @param resourcelist
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
private Bundle createBundle(List<IBaseResource> resourcelist) {
|
||||
Bundle bundle = new Bundle();
|
||||
for (IBaseResource resource : resourcelist) {
|
||||
Bundle.Entry entry = bundle.addEntry();
|
||||
entry.setResource((IResource) resource);
|
||||
private Subscription getLocalSubscription(String id) {
|
||||
if (id != null && !id.trim().isEmpty()) {
|
||||
int size = myRestHookSubscriptions.size();
|
||||
if (size > 0) {
|
||||
for (Subscription restHookSubscription : myRestHookSubscriptions) {
|
||||
if (id.equals(restHookSubscription.getIdElement().getIdPart())) {
|
||||
return restHookSubscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bundle.setTotal(resourcelist.size());
|
||||
|
||||
return bundle;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -378,8 +238,36 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
public void initSubscriptions() {
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_TYPE, new TokenParam(null, SubscriptionChannelTypeEnum.REST_HOOK.getCode()));
|
||||
map.add(Subscription.SP_STATUS, new TokenParam(null, SubscriptionStatusEnum.ACTIVE.getCode()));
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
for (IBaseResource resource : resourceList) {
|
||||
myRestHookSubscriptions.add((Subscription) resource);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotifyOnDelete() {
|
||||
return myNotifyOnDelete;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
try {
|
||||
executor = Executors.newFixedThreadPool(MAX_THREADS);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to get DAO from PROXY");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -390,40 +278,94 @@ public class RestHookSubscriptionDstu2Interceptor extends InterceptorAdapter imp
|
|||
private void removeLocalSubscription(String subscriptionId) {
|
||||
Subscription localSubscription = getLocalSubscription(subscriptionId);
|
||||
if (localSubscription != null) {
|
||||
restHookSubscriptions.remove(localSubscription);
|
||||
logger.info("Subscription removed: " + subscriptionId);
|
||||
myRestHookSubscriptions.remove(localSubscription);
|
||||
ourLog.info("Subscription removed: " + subscriptionId);
|
||||
} else {
|
||||
logger.info("Subscription not found in local list. Subscription id: " + subscriptionId);
|
||||
ourLog.info("Subscription not found in local list. Subscription id: " + subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription from cache
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
* Handles incoming resources. If the resource is a rest-hook subscription, it adds
|
||||
* it to the rest-hook subscription list. Otherwise it checks to see if the resource
|
||||
* matches any rest-hook subscriptions.
|
||||
*/
|
||||
private Subscription getLocalSubscription(String id) {
|
||||
if (id != null && !id.trim().isEmpty()) {
|
||||
int size = restHookSubscriptions.size();
|
||||
if (size > 0) {
|
||||
for (Subscription restHookSubscription : restHookSubscriptions) {
|
||||
if (id.equals(restHookSubscription.getId().getIdPart())) {
|
||||
return restHookSubscription;
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
IIdType idType = theResource.getIdElement();
|
||||
ourLog.info("resource created type: {}", theRequest.getResourceName());
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
if (subscription.getChannel() != null
|
||||
&& subscription.getChannel().getTypeElement().getValueAsEnum() == SubscriptionChannelTypeEnum.REST_HOOK
|
||||
&& subscription.getStatusElement().getValueAsEnum() == SubscriptionStatusEnum.REQUESTED) {
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
mySubscriptionDao.update(subscription);
|
||||
myRestHookSubscriptions.add(subscription);
|
||||
ourLog.info("Subscription was added. Id: " + subscription.getId());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, theRequest.getResourceName(), RestOperationTypeEnum.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check subscriptions to see if there is a matching subscription when there is delete
|
||||
*
|
||||
* @param theRequestDetails
|
||||
* A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theRequest
|
||||
* The incoming request
|
||||
* @param theResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||
* to indicate that the server itself should not also provide a response.
|
||||
*/
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
String resourceType = theRequest.getResourceName();
|
||||
IIdType idType = theResource.getIdElement();
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
String id = idType.getIdPart();
|
||||
removeLocalSubscription(id);
|
||||
} else {
|
||||
if (myNotifyOnDelete) {
|
||||
checkSubscriptions(idType, resourceType, RestOperationTypeEnum.DELETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Checks for updates to subscriptions or if an update to a resource matches
|
||||
* a rest-hook subscription
|
||||
*/
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
String resourceType = theRequest.getResourceName();
|
||||
IIdType idType = theResource.getIdElement();
|
||||
|
||||
public boolean isNotifyOnDelete() {
|
||||
return notifyOnDelete;
|
||||
ourLog.info("resource updated type: " + resourceType);
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
if (subscription.getChannel() != null && subscription.getChannel().getTypeElement().getValueAsEnum() == SubscriptionChannelTypeEnum.REST_HOOK) {
|
||||
removeLocalSubscription(subscription.getIdElement().getIdPart());
|
||||
|
||||
if (subscription.getStatusElement().getValueAsEnum() == SubscriptionStatusEnum.ACTIVE) {
|
||||
myRestHookSubscriptions.add(subscription);
|
||||
ourLog.info("Subscription was updated. Id: " + subscription.getId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType, RestOperationTypeEnum.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNotifyOnDelete(boolean notifyOnDelete) {
|
||||
this.notifyOnDelete = notifyOnDelete;
|
||||
this.myNotifyOnDelete = notifyOnDelete;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,26 +18,15 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.interceptor;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.dao.dstu3.FhirResourceDaoSubscriptionDstu3;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.service.TMinusService;
|
||||
import ca.uhn.fhir.jpa.thread.HttpRequestDstu3Job;
|
||||
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
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.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
|
@ -45,9 +34,7 @@ import org.apache.http.client.methods.HttpUriRequest;
|
|||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -57,181 +44,76 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.service.TMinusService;
|
||||
import ca.uhn.fhir.jpa.thread.HttpRequestDstu3Job;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
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.interceptor.IServerOperationInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
|
||||
public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter implements IServerOperationInterceptor {
|
||||
|
||||
private static volatile ExecutorService executor;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookSubscriptionDstu3Interceptor.class);
|
||||
|
||||
private final static int MAX_THREADS = 1;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myObservationDaoDstu3")
|
||||
private IFhirResourceDao<Observation> myObservationDao;
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu3")
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
@Autowired
|
||||
@Qualifier("myObservationDaoDstu3")
|
||||
private IFhirResourceDao<Observation> myObservationDao;
|
||||
private FhirContext myCtx;
|
||||
|
||||
private static volatile ExecutorService executor;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RestHookSubscriptionDstu3Interceptor.class);
|
||||
private final List<Subscription> restHookSubscriptions = new ArrayList<Subscription>();
|
||||
private boolean notifyOnDelete = false;
|
||||
|
||||
private final static int MAX_THREADS = 1;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
try {
|
||||
executor = Executors.newFixedThreadPool(MAX_THREADS);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to get DAO from PROXY");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
public void initSubscriptions() {
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_TYPE, new TokenParam(null, Subscription.SubscriptionChannelType.RESTHOOK.toCode()));
|
||||
map.add(Subscription.SP_STATUS, new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()));
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
for (IBaseResource resource : resourceList) {
|
||||
restHookSubscriptions.add((Subscription) resource);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming resources. If the resource is a rest-hook subscription, it adds
|
||||
* it to the rest-hook subscription list. Otherwise it checks to see if the resource
|
||||
* matches any rest-hook subscriptions.
|
||||
*
|
||||
* @param theDetails The request details
|
||||
* @param theResourceTable The actual created entity
|
||||
*/
|
||||
@Override
|
||||
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
String resourceType = theDetails.getResourceType();
|
||||
IIdType idType = theDetails.getId();
|
||||
logger.info("resource created type: " + resourceType);
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
Subscription subscription = (Subscription) theDetails.getResource();
|
||||
if (subscription.getChannel() != null
|
||||
&& subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK
|
||||
&& subscription.getStatus() == Subscription.SubscriptionStatus.REQUESTED) {
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
mySubscriptionDao.update(subscription);
|
||||
restHookSubscriptions.add(subscription);
|
||||
logger.info("Subscription was added. Id: " + subscription.getId());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates to subscriptions or if an update to a resource matches
|
||||
* a rest-hook subscription
|
||||
*
|
||||
* @param theDetails The request details
|
||||
* @param theResourceTable The actual updated entity
|
||||
*/
|
||||
@Override
|
||||
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
String resourceType = theDetails.getResourceType();
|
||||
IIdType idType = theDetails.getId();
|
||||
|
||||
logger.info("resource updated type: " + resourceType);
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
Subscription subscription = (Subscription) theDetails.getResource();
|
||||
if (subscription.getChannel() != null && subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK) {
|
||||
removeLocalSubscription(subscription.getIdElement().getIdPart());
|
||||
|
||||
if (subscription.getStatus() == Subscription.SubscriptionStatus.ACTIVE) {
|
||||
restHookSubscriptions.add(subscription);
|
||||
logger.info("Subscription was updated. Id: " + subscription.getId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check subscriptions to see if there is a matching subscription when there is delete
|
||||
*
|
||||
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theRequest The incoming request
|
||||
* @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||
* to indicate that the server itself should not also provide a response.
|
||||
* @return
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
@Override
|
||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
if (theRequestDetails.getRestOperationType().equals(RestOperationTypeEnum.DELETE)) {
|
||||
String resourceType = theRequestDetails.getResourceName();
|
||||
IIdType idType = theRequestDetails.getId();
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
String id = idType.getIdPart();
|
||||
removeLocalSubscription(id);
|
||||
} else {
|
||||
if (notifyOnDelete) {
|
||||
checkSubscriptions(idType, resourceType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse);
|
||||
}
|
||||
private final List<Subscription> restHookSubscriptions = new ArrayList<Subscription>();
|
||||
|
||||
/**
|
||||
* Check subscriptions and send notifications or payload
|
||||
*
|
||||
* @param idType
|
||||
* @param resourceType
|
||||
* @param theOperation
|
||||
*/
|
||||
private void checkSubscriptions(IIdType idType, String resourceType) {
|
||||
private void checkSubscriptions(IIdType idType, String resourceType, RestOperationTypeEnum theOperation) {
|
||||
/*
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
// map.add("_id", new StringParam("Observation/" + idType.getIdPart()));
|
||||
map.add("code", new TokenParam("SNOMED-CT", "1000000050"));
|
||||
//map.setLoadSynchronous(true);
|
||||
// Include include = new Include("nothing");
|
||||
// map.addInclude(include);
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IBundleProvider myBundle = myObservationDao.search(map, req);
|
||||
Observation myObservation = myObservationDao.read(idType);
|
||||
|
||||
int mysize = myBundle.size();
|
||||
List result = myBundle.getResources(0, myBundle.size());
|
||||
*/
|
||||
* SearchParameterMap map = new SearchParameterMap();
|
||||
* // map.add("_id", new StringParam("Observation/" + idType.getIdPart()));
|
||||
* map.add("code", new TokenParam("SNOMED-CT", "1000000050"));
|
||||
* //map.setLoadSynchronous(true);
|
||||
* // Include include = new Include("nothing");
|
||||
* // map.addInclude(include);
|
||||
*
|
||||
* RequestDetails req = new ServletSubRequestDetails();
|
||||
* req.setSubRequest(true);
|
||||
*
|
||||
* IBundleProvider myBundle = myObservationDao.search(map, req);
|
||||
* Observation myObservation = myObservationDao.read(idType);
|
||||
*
|
||||
* int mysize = myBundle.size();
|
||||
* List result = myBundle.getResources(0, myBundle.size());
|
||||
*/
|
||||
for (Subscription subscription : restHookSubscriptions) {
|
||||
//see if the criteria matches the created object
|
||||
logger.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
// see if the criteria matches the created object
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
logger.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
continue;
|
||||
}
|
||||
//run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
|
||||
// run the subscriptions query and look for matches, add the id as part of the criteria to avoid getting matches of previous resources rather than the recent resource
|
||||
String criteria = subscription.getCriteria();
|
||||
criteria += "&_id=" + idType.getResourceType() + "/" + idType.getIdPart();
|
||||
criteria = TMinusService.parseCriteria(criteria);
|
||||
|
@ -242,105 +124,78 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
continue;
|
||||
}
|
||||
|
||||
//should just be one resource as it was filtered by the id
|
||||
// should just be one resource as it was filtered by the id
|
||||
for (IBaseResource nextBase : results.getResources(0, results.size())) {
|
||||
IAnyResource next = (IAnyResource) nextBase;
|
||||
logger.info("Found match: queueing rest-hook notification for resource: {}", next.getIdElement());
|
||||
HttpUriRequest request = createRequest(subscription, next);
|
||||
ourLog.info("Found match: queueing rest-hook notification for resource: {}", next.getIdElement());
|
||||
HttpUriRequest request = createRequest(subscription, next, theOperation);
|
||||
if (request != null) {
|
||||
executor.submit(new HttpRequestDstu3Job(request, subscription));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTTP Post for a subscription
|
||||
*
|
||||
* @param subscription
|
||||
* @param resource
|
||||
* @return
|
||||
*/
|
||||
private HttpUriRequest createRequest(Subscription subscription, IAnyResource resource) {
|
||||
String url = subscription.getChannel().getEndpoint();
|
||||
private HttpUriRequest createRequest(Subscription theSubscription, IAnyResource theResource, RestOperationTypeEnum theOperation) {
|
||||
String url = theSubscription.getChannel().getEndpoint();
|
||||
while (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length() - 1);
|
||||
}
|
||||
|
||||
HttpUriRequest request = null;
|
||||
String resourceName = myCtx.getResourceDefinition(theResource).getName();
|
||||
|
||||
String payload = subscription.getChannel().getPayload();
|
||||
//HTTP post
|
||||
if (payload == null || payload.trim().length() == 0) {
|
||||
//return an empty response as there is no payload
|
||||
logger.info("No payload found, returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
//HTTP put
|
||||
else if (EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
logger.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
String payload = theSubscription.getChannel().getPayload();
|
||||
String resourceId = theResource.getIdElement().getIdPart();
|
||||
|
||||
// HTTP put
|
||||
if (theOperation == RestOperationTypeEnum.UPDATE && EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, theResource);
|
||||
HttpPut putRequest = new HttpPut(url + "/" + resourceName + "/" + resourceId);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_XML_NEW);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
//HTTP put
|
||||
else if (EncodingEnum.JSON.equals(EncodingEnum.forContentType(payload))) {
|
||||
logger.info("JSON payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
// HTTP put
|
||||
else if (theOperation == RestOperationTypeEnum.UPDATE && EncodingEnum.JSON.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("JSON payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, theResource);
|
||||
HttpPut putRequest = new HttpPut(url + "/" + resourceName + "/" + resourceId);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
//HTTP post
|
||||
else if (payload.startsWith("application/fhir+query/")) { //custom payload that is a FHIR query
|
||||
logger.info("Custom query payload found");
|
||||
String responseCriteria = subscription.getChannel().getPayload().substring(23);
|
||||
responseCriteria = TMinusService.parseCriteria(responseCriteria);
|
||||
// HTTP POST
|
||||
else if (theOperation == RestOperationTypeEnum.CREATE && EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, theResource);
|
||||
HttpPost putRequest = new HttpPost(url + "/" + resourceName);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_XML_NEW);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
//get the encoding type from payload which is a FHIR query with &_format=
|
||||
EncodingEnum encoding = getEncoding(responseCriteria);
|
||||
IBundleProvider responseResults = getBundleProvider(responseCriteria);
|
||||
if (responseResults.size() != 0) {
|
||||
List<IBaseResource> resourcelist = responseResults.getResources(0, responseResults.size());
|
||||
Bundle bundle = createBundle(resourcelist);
|
||||
StringEntity bundleEntity = getStringEntity(encoding, bundle);
|
||||
HttpPost postRequest = new HttpPost(url);
|
||||
postRequest.setEntity(bundleEntity);
|
||||
|
||||
request = postRequest;
|
||||
} else {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.setTotal(0);
|
||||
StringEntity bundleEntity = getStringEntity(encoding, bundle);
|
||||
HttpPost postRequest = new HttpPost(url);
|
||||
postRequest.setEntity(bundleEntity);
|
||||
|
||||
request = postRequest;
|
||||
request = putRequest;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unsupported payload " + payload + ". Returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
// HTTP POST
|
||||
else if (theOperation == RestOperationTypeEnum.CREATE && EncodingEnum.JSON.equals(EncodingEnum.forContentType(payload))) {
|
||||
ourLog.info("JSON payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.JSON, theResource);
|
||||
HttpPost putRequest = new HttpPost(url + "/" + resourceName);
|
||||
putRequest.addHeader(Constants.HEADER_CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW);
|
||||
putRequest.setEntity(entity);
|
||||
|
||||
request = putRequest;
|
||||
}
|
||||
|
||||
//request.addHeader("User-Agent", USER_AGENT);
|
||||
// request.addHeader("User-Agent", USER_AGENT);
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encoding from the criteria or return JSON encoding if its not found
|
||||
*
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
private EncodingEnum getEncoding(String criteria) {
|
||||
//check criteria
|
||||
String params = criteria.substring(criteria.indexOf('?') + 1);
|
||||
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
|
||||
for (NameValuePair nameValuePair : paramValues) {
|
||||
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
|
||||
return EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
}
|
||||
}
|
||||
return EncodingEnum.JSON;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search based on a query criteria
|
||||
*
|
||||
|
@ -360,59 +215,21 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a bundle to return to the client
|
||||
* Get the encoding from the criteria or return JSON encoding if its not found
|
||||
*
|
||||
* @param resourcelist
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
private Bundle createBundle(List<IBaseResource> resourcelist) {
|
||||
Bundle bundle = new Bundle();
|
||||
for (IBaseResource resource : resourcelist) {
|
||||
Bundle.BundleEntryComponent entry = bundle.addEntry();
|
||||
entry.setResource((Resource) resource);
|
||||
private EncodingEnum getEncoding(String criteria) {
|
||||
// check criteria
|
||||
String params = criteria.substring(criteria.indexOf('?') + 1);
|
||||
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
|
||||
for (NameValuePair nameValuePair : paramValues) {
|
||||
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
|
||||
return EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
}
|
||||
bundle.setTotal(resourcelist.size());
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a resource into a string entity
|
||||
*
|
||||
* @param encoding
|
||||
* @param anyResource
|
||||
* @return
|
||||
*/
|
||||
private StringEntity getStringEntity(EncodingEnum encoding, IAnyResource anyResource) {
|
||||
String encoded = encoding.newParser(mySubscriptionDao.getContext()).encodeResourceToString(anyResource);
|
||||
|
||||
StringEntity entity;
|
||||
if (encoded.equalsIgnoreCase(EncodingEnum.JSON.name())) {
|
||||
entity = new StringEntity(encoded, ContentType.APPLICATION_JSON);
|
||||
} else {
|
||||
entity = new StringEntity(encoded, ContentType.APPLICATION_XML);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscription from cache
|
||||
*
|
||||
* @param subscriptionId
|
||||
*/
|
||||
private void removeLocalSubscription(String subscriptionId) {
|
||||
Subscription localSubscription = getLocalSubscription(subscriptionId);
|
||||
if (localSubscription != null) {
|
||||
restHookSubscriptions.remove(localSubscription);
|
||||
logger.info("Subscription removed: " + subscriptionId);
|
||||
} else {
|
||||
logger.info("Subscription not found in local list. Subscription id: " + subscriptionId);
|
||||
}
|
||||
return EncodingEnum.JSON;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -436,10 +253,154 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a resource into a string entity
|
||||
*
|
||||
* @param encoding
|
||||
* @param anyResource
|
||||
* @return
|
||||
*/
|
||||
private StringEntity getStringEntity(EncodingEnum encoding, IAnyResource anyResource) {
|
||||
String encoded = encoding.newParser(mySubscriptionDao.getContext()).encodeResourceToString(anyResource);
|
||||
|
||||
StringEntity entity;
|
||||
if (encoded.equalsIgnoreCase(EncodingEnum.JSON.name())) {
|
||||
entity = new StringEntity(encoded, ContentType.APPLICATION_JSON);
|
||||
} else {
|
||||
entity = new StringEntity(encoded, ContentType.APPLICATION_XML);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
public void initSubscriptions() {
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_TYPE, new TokenParam(null, Subscription.SubscriptionChannelType.RESTHOOK.toCode()));
|
||||
map.add(Subscription.SP_STATUS, new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode()));
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IBundleProvider subscriptionBundleList = mySubscriptionDao.search(map, req);
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
for (IBaseResource resource : resourceList) {
|
||||
restHookSubscriptions.add((Subscription) resource);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isNotifyOnDelete() {
|
||||
return notifyOnDelete;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
try {
|
||||
executor = Executors.newFixedThreadPool(MAX_THREADS);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to get DAO from PROXY");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove subscription from cache
|
||||
*
|
||||
* @param subscriptionId
|
||||
*/
|
||||
private void removeLocalSubscription(String subscriptionId) {
|
||||
Subscription localSubscription = getLocalSubscription(subscriptionId);
|
||||
if (localSubscription != null) {
|
||||
restHookSubscriptions.remove(localSubscription);
|
||||
ourLog.info("Subscription removed: " + subscriptionId);
|
||||
} else {
|
||||
ourLog.info("Subscription not found in local list. Subscription id: " + subscriptionId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles incoming resources. If the resource is a rest-hook subscription, it adds
|
||||
* it to the rest-hook subscription list. Otherwise it checks to see if the resource
|
||||
* matches any rest-hook subscriptions.
|
||||
*/
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
IIdType idType = theResource.getIdElement();
|
||||
ourLog.info("resource created type: {}", theRequest.getResourceName());
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
if (subscription.getChannel() != null
|
||||
&& subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK
|
||||
&& subscription.getStatus() == Subscription.SubscriptionStatus.REQUESTED) {
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
|
||||
mySubscriptionDao.update(subscription);
|
||||
restHookSubscriptions.add(subscription);
|
||||
ourLog.info("Subscription was added. Id: " + subscription.getId());
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, theRequest.getResourceName(), RestOperationTypeEnum.CREATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check subscriptions to see if there is a matching subscription when there is delete
|
||||
*
|
||||
* @param theRequestDetails
|
||||
* A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theRequest
|
||||
* The incoming request
|
||||
* @param theResponse
|
||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||
* to indicate that the server itself should not also provide a response.
|
||||
*/
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
String resourceType = theRequest.getResourceName();
|
||||
IIdType idType = theResource.getIdElement();
|
||||
|
||||
if (resourceType.equals(Subscription.class.getSimpleName())) {
|
||||
String id = idType.getIdPart();
|
||||
removeLocalSubscription(id);
|
||||
} else {
|
||||
if (notifyOnDelete) {
|
||||
checkSubscriptions(idType, resourceType, RestOperationTypeEnum.DELETE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates to subscriptions or if an update to a resource matches
|
||||
* a rest-hook subscription
|
||||
*/
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
String resourceType = theRequest.getResourceName();
|
||||
IIdType idType = theResource.getIdElement();
|
||||
|
||||
ourLog.info("resource updated type: " + resourceType);
|
||||
|
||||
if (theResource instanceof Subscription) {
|
||||
Subscription subscription = (Subscription) theResource;
|
||||
if (subscription.getChannel() != null && subscription.getChannel().getType() == Subscription.SubscriptionChannelType.RESTHOOK) {
|
||||
removeLocalSubscription(subscription.getIdElement().getIdPart());
|
||||
|
||||
if (subscription.getStatus() == Subscription.SubscriptionStatus.ACTIVE) {
|
||||
restHookSubscriptions.add(subscription);
|
||||
ourLog.info("Subscription was updated. Id: " + subscription.getId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checkSubscriptions(idType, resourceType, RestOperationTypeEnum.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNotifyOnDelete(boolean notifyOnDelete) {
|
||||
this.notifyOnDelete = notifyOnDelete;
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -37,35 +38,28 @@ import javax.annotation.PostConstruct;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class WebSocketSubscriptionDstu3Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
public class WebSocketSubscriptionDstu3Interceptor extends InterceptorAdapter implements IServerOperationInterceptor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketSubscriptionDstu3Interceptor.class);
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(WebSocketSubscriptionDstu3Interceptor.class);
|
||||
|
||||
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDaoCasted;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu3")
|
||||
private IFhirResourceDao<Subscription> reference;
|
||||
|
||||
private IFhirResourceDaoSubscription<Subscription> casted;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct(){
|
||||
casted = (IFhirResourceDaoSubscription) reference;
|
||||
}
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
|
||||
@Override
|
||||
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
if (theRequestDetails.getRestOperationType().equals(RestOperationTypeEnum.DELETE)) {
|
||||
mySubscriptionDaoCasted.pollForNewUndeliveredResources(theRequestDetails.getResourceName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for websocket subscriptions
|
||||
*
|
||||
* @param theRequestDetails
|
||||
* A bean containing details about the request that is about to be processed, including details such as the
|
||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||
|
@ -83,19 +77,31 @@ public class WebSocketSubscriptionDstu3Interceptor extends InterceptorAdapter im
|
|||
}
|
||||
|
||||
if (theRequestDetails.getRequestType().equals(RequestTypeEnum.POST) || theRequestDetails.getRequestType().equals(RequestTypeEnum.PUT)) {
|
||||
logger.info("Found POST or PUT for a non-subscription resource");
|
||||
casted.pollForNewUndeliveredResources(theRequestDetails.getResourceName());
|
||||
ourLog.info("Found POST or PUT for a non-subscription resource");
|
||||
mySubscriptionDaoCasted.pollForNewUndeliveredResources(theRequestDetails.getResourceName());
|
||||
}
|
||||
|
||||
return super.outgoingResponse(theRequestDetails, theResponseObject);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
||||
if (theRequestDetails.getRestOperationType().equals(RestOperationTypeEnum.DELETE)) {
|
||||
casted.pollForNewUndeliveredResources(theRequestDetails.getResourceName());
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
mySubscriptionDaoCasted = (IFhirResourceDaoSubscription) mySubscriptionDao;
|
||||
}
|
||||
|
||||
return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse);
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
// nothing
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import org.springframework.web.servlet.DispatcherServlet;
|
|||
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.BundleEntry;
|
||||
|
@ -43,50 +44,16 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
protected static IGenericClient ourClient;
|
||||
protected static CloseableHttpClient ourHttpClient;
|
||||
protected static int ourPort;
|
||||
protected static RestfulServer ourRestServer;
|
||||
private static Server ourServer;
|
||||
protected static String ourServerBase;
|
||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static RestfulServer ourRestServer;
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
protected static RestHookSubscriptionDstu2Interceptor ourRestHookSubscriptionInterceptor;
|
||||
|
||||
public BaseResourceProviderDstu2Test() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
|
||||
List<IdDt> list = new ArrayList<IdDt>();
|
||||
for (BundleEntry next : found.getEntries()) {
|
||||
list.add(next.getResource().getId().toUnqualifiedVersionless());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
protected List<String> toNameList(Bundle resp) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (BundleEntry next : resp.getEntries()) {
|
||||
Patient nextPt = (Patient) next.getResource();
|
||||
String nextStr = nextPt.getNameFirstRep().getGivenAsSingleString() + " " + nextPt.getNameFirstRep().getFamilyAsSingleString();
|
||||
if (isNotBlank(nextStr)) {
|
||||
names.add(nextStr);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
ourHttpClient.close();
|
||||
ourServer = null;
|
||||
ourHttpClient = null;
|
||||
ourWebApplicationContext.close();
|
||||
ourWebApplicationContext = null;
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
|
@ -131,13 +98,18 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourWebApplicationContext.setParent(myAppCtx);
|
||||
ourWebApplicationContext.refresh();
|
||||
|
||||
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(RestHookSubscriptionDstu2Interceptor.class);
|
||||
|
||||
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
|
||||
|
||||
DispatcherServlet dispatcherServlet = new DispatcherServlet();
|
||||
dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class);
|
||||
ServletHolder subsServletHolder = new ServletHolder();
|
||||
subsServletHolder.setServlet(dispatcherServlet);
|
||||
subsServletHolder.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, WebsocketDstu2Config.class.getName() + "\n" + WebsocketDstu2DispatcherConfig.class.getName());
|
||||
subsServletHolder.setInitParameter(
|
||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||
WebsocketDstu2Config.class.getName() + "\n" +
|
||||
WebsocketDstu2DispatcherConfig.class.getName());
|
||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||
|
||||
|
||||
|
@ -156,4 +128,35 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
}
|
||||
}
|
||||
|
||||
protected List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
|
||||
List<IdDt> list = new ArrayList<IdDt>();
|
||||
for (BundleEntry next : found.getEntries()) {
|
||||
list.add(next.getResource().getId().toUnqualifiedVersionless());
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
protected List<String> toNameList(Bundle resp) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (BundleEntry next : resp.getEntries()) {
|
||||
Patient nextPt = (Patient) next.getResource();
|
||||
String nextStr = nextPt.getNameFirstRep().getGivenAsSingleString() + " " + nextPt.getNameFirstRep().getFamilyAsSingleString();
|
||||
if (isNotBlank(nextStr)) {
|
||||
names.add(nextStr);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContextBaseResourceProviderDstu3Test() throws Exception {
|
||||
ourServer.stop();
|
||||
ourHttpClient.close();
|
||||
ourServer = null;
|
||||
ourHttpClient = null;
|
||||
ourWebApplicationContext.close();
|
||||
ourWebApplicationContext = null;
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
}
|
|
@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3Config;
|
|||
import ca.uhn.fhir.jpa.config.dstu3.WebsocketDstu3DispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
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;
|
||||
|
@ -55,6 +56,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
protected static String ourServerBase;
|
||||
private static GenericWebApplicationContext ourWebApplicationContext;
|
||||
private TerminologyUploaderProviderDstu3 myTerminologyUploaderProvider;
|
||||
protected static RestHookSubscriptionDstu3Interceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static ISearchDao mySearchEntityDao;
|
||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
|
||||
|
@ -117,7 +119,10 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class);
|
||||
ServletHolder subsServletHolder = new ServletHolder();
|
||||
subsServletHolder.setServlet(dispatcherServlet);
|
||||
subsServletHolder.setInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, WebsocketDstu3Config.class.getName() + "\n" + WebsocketDstu3DispatcherConfig.class.getName());
|
||||
subsServletHolder.setInitParameter(
|
||||
ContextLoader.CONFIG_LOCATION_PARAM,
|
||||
WebsocketDstu3Config.class.getName() + "\n" +
|
||||
WebsocketDstu3DispatcherConfig.class.getName());
|
||||
proxyHandler.addServlet(subsServletHolder, "/*");
|
||||
|
||||
// Register a CORS filter
|
||||
|
@ -143,6 +148,7 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
|
||||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
||||
ourRestHookSubscriptionInterceptor = wac.getBean(RestHookSubscriptionDstu3Interceptor.class);
|
||||
|
||||
ourClient = myFhirCtx.newRestfulGenericClient(ourServerBase);
|
||||
ourClient.registerInterceptor(new LoggingInterceptor(true));
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
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.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
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.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createSubscription' test
|
||||
* 2. Update the subscription id in the 'attachWebSocket' test
|
||||
* 3. Execute the 'attachWebSocket' test
|
||||
* 4. Execute the 'sendObservation' test
|
||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithCriteriaDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu2Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
||||
Patient patient = FhirDstu2Util.getPatient();
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(patient).execute();
|
||||
myPatientId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
// subscription.setCriteria("Observation?subject=Patient/" + PATIENT_ID);
|
||||
subscription.setCriteria("Observation?code=SNOMED-CT|82313006&_format=xml");
|
||||
|
||||
Channel channel = new Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
mySubscriptionId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
myWebSocketClient = new WebSocketClient();
|
||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterCloseWebsocket() throws Exception {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
myWebSocketClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConceptDt cc = new CodeableConceptDt();
|
||||
observation.setCode(cc);
|
||||
CodingDt coding = cc.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ResourceReferenceDt reference = new ResourceReferenceDt();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservationThatDoesNotMatch() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConceptDt cc = new CodeableConceptDt();
|
||||
observation.setCode(cc);
|
||||
CodingDt coding = cc.addCoding();
|
||||
coding.setCode("8231");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ResourceReferenceDt reference = new ResourceReferenceDt();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.CodeableConcept;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createPatient' test
|
||||
* 2. Update the patient id static variable
|
||||
* 3. Execute the 'createSubscription' test
|
||||
* 4. Update the subscription id static variable
|
||||
* 5. Execute the 'attachWebSocket' test
|
||||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithCriteriaDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithCriteriaDstu3Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
||||
Patient patient = FhirDstu3Util.getPatient();
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(patient).execute();
|
||||
myPatientId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
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("Observation?subject=Patient/" + PATIENT_ID);
|
||||
subscription.setCriteria("Observation?code=SNOMED-CT|82313006&_format=xml");
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
mySubscriptionId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
myWebSocketClient = new WebSocketClient();
|
||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterCloseWebsocket() throws Exception {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
myWebSocketClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
Reference reference = new Reference();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservationThatDoesNotMatch() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("8231");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
Reference reference = new Reference();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
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.composite.ResourceReferenceDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||
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.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createSubscription' test
|
||||
* 2. Update the subscription id in the 'attachWebSocket' test
|
||||
* 3. Execute the 'attachWebSocket' test
|
||||
* 4. Execute the 'sendObservation' test
|
||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu2Test extends BaseResourceProviderDstu2Test {
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
||||
Patient patient = FhirDstu2Util.getPatient();
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(patient).execute();
|
||||
myPatientId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
// subscription.setCriteria("Observation?subject=Patient/" + PATIENT_ID);
|
||||
subscription.setCriteria("Observation?code=SNOMED-CT|82313006");
|
||||
|
||||
Channel channel = new Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
mySubscriptionId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
myWebSocketClient = new WebSocketClient();
|
||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu2");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterCloseWebsocket() throws Exception {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
myWebSocketClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConceptDt cc = new CodeableConceptDt();
|
||||
observation.setCode(cc);
|
||||
CodingDt coding = cc.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ResourceReferenceDt reference = new ResourceReferenceDt();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservationThatDoesNotMatch() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConceptDt cc = new CodeableConceptDt();
|
||||
observation.setCode(cc);
|
||||
CodingDt coding = cc.addCoding();
|
||||
coding.setCode("8231");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
ResourceReferenceDt reference = new ResourceReferenceDt();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.CodeableConcept;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Reference;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3Test for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createPatient' test
|
||||
* 2. Update the patient id static variable
|
||||
* 3. Execute the 'createSubscription' test
|
||||
* 4. Update the subscription id static variable
|
||||
* 5. Execute the 'attachWebSocket' test
|
||||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class);
|
||||
|
||||
private String myPatientId;
|
||||
private String mySubscriptionId;
|
||||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
myDaoConfig.setSubscriptionEnabled(new DaoConfig().isSubscriptionEnabled());
|
||||
myDaoConfig.setSubscriptionPollDelay(new DaoConfig().getSubscriptionPollDelay());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
/*
|
||||
* Create patient
|
||||
*/
|
||||
|
||||
Patient patient = FhirDstu3Util.getPatient();
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(patient).execute();
|
||||
myPatientId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Create subscription
|
||||
*/
|
||||
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("Observation?subject=Patient/" + PATIENT_ID);
|
||||
subscription.setCriteria("Observation?code=SNOMED-CT|82313006");
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
methodOutcome = ourClient.create().resource(subscription).execute();
|
||||
mySubscriptionId = methodOutcome.getId().getIdPart();
|
||||
|
||||
/*
|
||||
* Attach websocket
|
||||
*/
|
||||
|
||||
myWebSocketClient = new WebSocketClient();
|
||||
mySocketImplementation = new SocketImplementation(mySubscriptionId, EncodingEnum.JSON);
|
||||
|
||||
myWebSocketClient.start();
|
||||
URI echoUri = new URI("ws://localhost:" + ourPort + "/websocket/dstu3");
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
|
||||
Session session = connection.get(2, TimeUnit.SECONDS);
|
||||
|
||||
ourLog.info("Connected to WS: {}", session.isOpen());
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterCloseWebsocket() throws Exception {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
myWebSocketClient.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
Reference reference = new Reference();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(1, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId, "ping " + mySubscriptionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservationThatDoesNotMatch() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("8231");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
Reference reference = new Reference();
|
||||
reference.setReference("Patient/" + myPatientId);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome2 = ourClient.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
ourLog.info("Observation id generated by server is: " + observationId);
|
||||
|
||||
int changes = mySubscriptionDao.pollForNewUndeliveredResources();
|
||||
ourLog.info("Polling showed {}", changes);
|
||||
assertEquals(0, changes);
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
ourLog.info("WS Messages: {}", mySocketImplementation.getMessages());
|
||||
assertThat(mySocketImplementation.getMessages(), contains("bound " + mySubscriptionId));
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.gclient.IQuery;
|
|
@ -16,7 +16,7 @@
|
|||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
|
@ -39,7 +39,7 @@ import org.slf4j.Logger;
|
|||
@Ignore
|
||||
public class RestHookTestDstu2IT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class);
|
||||
private static String code = "1000000012";
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu2Client();
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
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.*;
|
||||
|
||||
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.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
public class RestHookTestDstu2Test 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(RestHookTestDstu2Test.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());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
// ourRestHookSubscriptionInterceptor.set
|
||||
ourRestServer.registerInterceptor(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.REQUESTED);
|
||||
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 = RandomServerPortProvider.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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
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.dstu3.model.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.*;
|
||||
|
||||
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.dstu3.BaseResourceProviderDstu3Test;
|
||||
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.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
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
|
||||
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());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
// ourRestHookSubscriptionInterceptor.set
|
||||
ourRestServer.registerInterceptor(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(Subscription.SubscriptionStatus.REQUESTED);
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
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();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode(code);
|
||||
coding.setSystem(system);
|
||||
|
||||
observation.setStatus(Observation.ObservationStatus.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());
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation3.setCode(codeableConcept);
|
||||
Coding 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());
|
||||
|
||||
CodeableConcept codeableConcept1 = new CodeableConcept();
|
||||
observation3a.setCode(codeableConcept1);
|
||||
Coding 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());
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation3.setCode(codeableConcept);
|
||||
Coding 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());
|
||||
|
||||
CodeableConcept codeableConcept1 = new CodeableConcept();
|
||||
observation3a.setCode(codeableConcept1);
|
||||
Coding 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 = RandomServerPortProvider.findFreePort();
|
||||
ourListenerRestServer = new RestfulServer(FhirContext.forDstu3());
|
||||
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 IdType("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 IdType("Observation/1"), false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
@ -36,7 +36,7 @@ import org.slf4j.Logger;
|
|||
@Ignore
|
||||
public class RestHookTestDstu3WithSubscriptionResponseCriteriaIT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3Test.class);
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscription() {
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
|
||||
@WebSocket
|
||||
public class SocketImplementation {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(SocketImplementation.class);
|
||||
private String myCriteria;
|
||||
protected String myError;
|
||||
protected boolean myGotBound;
|
||||
private List<String> myMessages = new ArrayList<String>();
|
||||
protected int myPingCount;
|
||||
protected String mySubsId;
|
||||
private Session session;
|
||||
|
||||
public SocketImplementation(String theCriteria, EncodingEnum theEncoding) {
|
||||
myCriteria = theCriteria;
|
||||
}
|
||||
|
||||
public List<String> getMessages() {
|
||||
return myMessages;
|
||||
}
|
||||
|
||||
public void keepAlive() {
|
||||
if (this.session != null) {
|
||||
try {
|
||||
session.getRemote().sendString("keep alive");
|
||||
} catch (Throwable t) {
|
||||
ourLog.error("Failure", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed when the client is connecting to the server.
|
||||
* In this case, we are sending a message to create the subscription dynamiclly
|
||||
*
|
||||
* @param session
|
||||
*/
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
ourLog.info("Got connect: {}", session);
|
||||
this.session = session;
|
||||
try {
|
||||
String sending = "bind " + myCriteria;
|
||||
ourLog.info("Sending: {}", sending);
|
||||
session.getRemote().sendString(sending);
|
||||
|
||||
ourLog.info("Connection: DONE");
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ourLog.error("Failure", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the message handler for the client
|
||||
*
|
||||
* @param theMsg
|
||||
*/
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String theMsg) {
|
||||
ourLog.info("Got msg: " + theMsg);
|
||||
myMessages.add(theMsg);
|
||||
|
||||
if (theMsg.startsWith("bound ")) {
|
||||
myGotBound = true;
|
||||
mySubsId = (theMsg.substring("bound ".length()));
|
||||
} else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) {
|
||||
String text = theMsg.substring(("add " + mySubsId + "\n").length());
|
||||
ourLog.info("text: " + text);
|
||||
} else {
|
||||
myError = "Unexpected message: " + theMsg;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.jpa.service.TMinusService;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
|
@ -206,6 +206,15 @@
|
|||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
For some reason JavaDoc crashed during site generation unless we have this dependency
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
<artifactId>javax.interceptor-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -11,23 +11,23 @@ import org.hibernate.jpa.HibernatePersistenceProvider;
|
|||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
|
||||
import ca.uhn.fhir.jpa.config.WebsocketDstu2Config;
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
|
||||
/**
|
||||
* This is the primary configuration file for the example server
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement()
|
||||
//@Import(WebsocketDstu2Config.class)
|
||||
public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
||||
public class FhirServerConfig extends BaseJavaConfigDstu3 {
|
||||
|
||||
/**
|
||||
* Configure FHIR properties around the the JPA server via this bean
|
||||
|
@ -83,6 +83,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
|
||||
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
|
||||
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
|
||||
// extraProperties.put("hibernate.search.default.worker.execution", "async");
|
||||
return extraProperties;
|
||||
}
|
||||
|
||||
|
@ -110,7 +111,7 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor subscriptionSecurityInterceptor() {
|
||||
SubscriptionsRequireManualActivationInterceptorDstu2 retVal = new SubscriptionsRequireManualActivationInterceptorDstu2();
|
||||
SubscriptionsRequireManualActivationInterceptorDstu3 retVal = new SubscriptionsRequireManualActivationInterceptorDstu3();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
|
||||
/**
|
||||
* This class isn't used by default by the example, but
|
||||
* you can use it as a config if you want to support DSTU3
|
||||
* instead of DSTU2 in your server.
|
||||
*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/278
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement()
|
||||
public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
|
||||
|
||||
/**
|
||||
* Configure FHIR properties around the the JPA server via this bean
|
||||
*/
|
||||
@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(true);
|
||||
retVal.setSubscriptionPollDelay(5000);
|
||||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a
|
||||
* directory called "jpaserver_derby_files".
|
||||
*
|
||||
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
|
||||
*/
|
||||
@Bean(destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver());
|
||||
retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true");
|
||||
retVal.setUsername("");
|
||||
retVal.setPassword("");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
|
||||
retVal.setPersistenceUnitName("HAPI_PU");
|
||||
retVal.setDataSource(dataSource());
|
||||
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
|
||||
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
|
||||
retVal.setJpaProperties(jpaProperties());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Properties jpaProperties() {
|
||||
Properties extraProperties = new Properties();
|
||||
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
|
||||
extraProperties.put("hibernate.format_sql", "true");
|
||||
extraProperties.put("hibernate.show_sql", "false");
|
||||
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
||||
extraProperties.put("hibernate.jdbc.batch_size", "20");
|
||||
extraProperties.put("hibernate.cache.use_query_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_structured_entries", "false");
|
||||
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
|
||||
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
|
||||
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
|
||||
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
|
||||
// extraProperties.put("hibernate.search.default.worker.execution", "async");
|
||||
return extraProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some fancy logging to create a nice access log that has details about each incoming request.
|
||||
*/
|
||||
public IServerInterceptor loggingInterceptor() {
|
||||
LoggingInterceptor retVal = new LoggingInterceptor();
|
||||
retVal.setLoggerName("fhirtest.access");
|
||||
retVal.setMessageFormat(
|
||||
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
|
||||
retVal.setLogExceptions(true);
|
||||
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
|
||||
*/
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor responseHighlighterInterceptor() {
|
||||
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor subscriptionSecurityInterceptor() {
|
||||
SubscriptionsRequireManualActivationInterceptorDstu3 retVal = new SubscriptionsRequireManualActivationInterceptorDstu3();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.WebSocketSubscriptionDstu3Interceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketReturnResourceHandlerDstu3;
|
||||
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* This class isn't used by default by the example, but
|
||||
* you can use it as a config if you want to support DSTU3
|
||||
* instead of DSTU2 in your server as well as rest-hook subscriptions,
|
||||
* event driven web-socket subscriptions, and a mysql database.
|
||||
*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/278
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket()
|
||||
@EnableTransactionManagement()
|
||||
public class FhirServerConfigDstu3WSocket extends BaseJavaConfigDstu3 implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
|
||||
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu3");
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public WebSocketHandler subscriptionWebSocketHandler() {
|
||||
PerConnectionWebSocketHandler retVal = new PerConnectionWebSocketHandler(SubscriptionWebsocketReturnResourceHandlerDstu3.class);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(destroyMethod="destroy")
|
||||
public TaskScheduler websocketTaskScheduler() {
|
||||
final ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
getScheduledThreadPoolExecutor().setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
// getScheduledThreadPoolExecutor().setRemoveOnCancelPolicy(true);
|
||||
getScheduledThreadPoolExecutor().setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
|
||||
}
|
||||
};
|
||||
retVal.setThreadNamePrefix("ws-dstu3-");
|
||||
retVal.setPoolSize(5);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public IServerInterceptor webSocketSubscriptionDstu3Interceptor(){
|
||||
return new WebSocketSubscriptionDstu3Interceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public IServerInterceptor restHookSubscriptionDstu3Interceptor(){
|
||||
return new RestHookSubscriptionDstu3Interceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure FHIR properties around the the JPA server via this bean
|
||||
*/
|
||||
@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(true);
|
||||
retVal.setSubscriptionPollDelay(-1000);
|
||||
retVal.setSchedulingDisabled(true);
|
||||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
retVal.setAllowExternalReferences(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the rest-hook and websocket interceptors after the DaoConfig bean has been
|
||||
* initialized to avoid cyclical dependency errors
|
||||
* @param daoConfig
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "subscriptionInterceptors")
|
||||
@DependsOn("daoConfig")
|
||||
public List<IServerInterceptor> afterDaoConfig(DaoConfig daoConfig){
|
||||
IServerInterceptor webSocketInterceptor = webSocketSubscriptionDstu3Interceptor();
|
||||
IServerInterceptor restHookInterceptor = restHookSubscriptionDstu3Interceptor();
|
||||
|
||||
try {
|
||||
RestHookSubscriptionDstu3Interceptor restHook = SpringObjectCaster.getTargetObject(restHookInterceptor, RestHookSubscriptionDstu3Interceptor.class);
|
||||
restHook.setNotifyOnDelete(true);
|
||||
restHook.initSubscriptions();
|
||||
}catch(PersistenceException e){
|
||||
throw new RuntimeException("Persistence error in setting up resthook subscriptions:" + e.getMessage());
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException("Unable to cast from proxy");
|
||||
}
|
||||
|
||||
daoConfig.getInterceptors().add(restHookInterceptor);
|
||||
daoConfig.getInterceptors().add(webSocketInterceptor);
|
||||
|
||||
return daoConfig.getInterceptors();
|
||||
}
|
||||
|
||||
/**
|
||||
* The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a
|
||||
* directory called "jpaserver_derby_files".
|
||||
*
|
||||
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
|
||||
*/
|
||||
@Bean(destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver());
|
||||
retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true");
|
||||
retVal.setUsername("");
|
||||
retVal.setPassword("");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
|
||||
retVal.setPersistenceUnitName("HAPI_PU");
|
||||
retVal.setDataSource(dataSource());
|
||||
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
|
||||
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
|
||||
retVal.setJpaProperties(jpaProperties());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Properties jpaProperties() {
|
||||
Properties extraProperties = new Properties();
|
||||
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
|
||||
extraProperties.put("hibernate.format_sql", "true");
|
||||
extraProperties.put("hibernate.show_sql", "false");
|
||||
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
||||
extraProperties.put("hibernate.jdbc.batch_size", "20");
|
||||
extraProperties.put("hibernate.cache.use_query_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_structured_entries", "false");
|
||||
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
|
||||
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
|
||||
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
|
||||
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
|
||||
return extraProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some fancy logging to create a nice access log that has details about each incoming request.
|
||||
*/
|
||||
public IServerInterceptor loggingInterceptor() {
|
||||
LoggingInterceptor retVal = new LoggingInterceptor();
|
||||
retVal.setLoggerName("fhirtest.access");
|
||||
retVal.setMessageFormat(
|
||||
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
|
||||
retVal.setLogExceptions(true);
|
||||
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
|
||||
*/
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor responseHighlighterInterceptor() {
|
||||
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor subscriptionSecurityInterceptor() {
|
||||
SubscriptionsRequireManualActivationInterceptorDstu3 retVal = new SubscriptionsRequireManualActivationInterceptorDstu3();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.interceptor.RestHookSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.WebSocketSubscriptionDstu2Interceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionWebsocketReturnResourceHandlerDstu2;
|
||||
import ca.uhn.fhir.jpa.util.SpringObjectCaster;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu2;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.DependsOn;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
import org.springframework.web.socket.config.annotation.EnableWebSocket;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
|
||||
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
|
||||
import org.springframework.web.socket.handler.PerConnectionWebSocketHandler;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.sql.DataSource;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* This class isn't used by default by the example, but
|
||||
* you can use it as a config if you want to support DSTU2 rest-hook subscriptions,
|
||||
* event driven web-socket subscriptions, and a mysql database.
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSocket()
|
||||
@EnableTransactionManagement()
|
||||
public class FhirServerConfigWSocket extends BaseJavaConfigDstu2 implements WebSocketConfigurer {
|
||||
|
||||
@Override
|
||||
public void registerWebSocketHandlers(WebSocketHandlerRegistry theRegistry) {
|
||||
theRegistry.addHandler(subscriptionWebSocketHandler(), "/websocket/dstu2");
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public WebSocketHandler subscriptionWebSocketHandler() {
|
||||
//return new PerConnectionWebSocketHandler(SubscriptionWebsocketHandlerDstu2.class);
|
||||
return new PerConnectionWebSocketHandler(SubscriptionWebsocketReturnResourceHandlerDstu2.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler websocketTaskScheduler() {
|
||||
ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
|
||||
retVal.setPoolSize(5);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure FHIR properties around the the JPA server via this bean
|
||||
*/
|
||||
@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
DaoConfig retVal = new DaoConfig();
|
||||
retVal.setSubscriptionEnabled(true);
|
||||
retVal.setSubscriptionPollDelay(-1000);
|
||||
retVal.setSchedulingDisabled(true);
|
||||
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
|
||||
retVal.setAllowMultipleDelete(true);
|
||||
retVal.setAllowExternalReferences(true);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the rest-hook and websocket interceptors after the DaoConfig bean has been
|
||||
* initialized to avoid cyclical dependency errors
|
||||
* @param daoConfig
|
||||
* @return
|
||||
*/
|
||||
@Bean(name = "subscriptionInterceptors")
|
||||
@DependsOn("daoConfig")
|
||||
public List<IServerInterceptor> afterDaoConfig(DaoConfig daoConfig){
|
||||
IServerInterceptor webSocketInterceptor = webSocketSubscriptionDstu2Interceptor();
|
||||
IServerInterceptor restHookInterceptor = restHookSubscriptionDstu2Interceptor();
|
||||
|
||||
try {
|
||||
RestHookSubscriptionDstu2Interceptor restHook = SpringObjectCaster.getTargetObject(restHookInterceptor, RestHookSubscriptionDstu2Interceptor.class);
|
||||
restHook.setNotifyOnDelete(true);
|
||||
restHook.initSubscriptions();
|
||||
}catch(PersistenceException e){
|
||||
throw new RuntimeException("Persistence error in setting up resthook subscriptions:" + e.getMessage());
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException("Unable to cast from proxy");
|
||||
}
|
||||
|
||||
daoConfig.getInterceptors().add(restHookInterceptor);
|
||||
daoConfig.getInterceptors().add(webSocketInterceptor);
|
||||
return daoConfig.getInterceptors();
|
||||
}
|
||||
|
||||
/**
|
||||
* The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a
|
||||
* directory called "jpaserver_derby_files".
|
||||
*
|
||||
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
|
||||
*/
|
||||
@Bean(destroyMethod = "close")
|
||||
public DataSource dataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource();
|
||||
retVal.setDriver(new org.apache.derby.jdbc.EmbeddedDriver());
|
||||
retVal.setUrl("jdbc:derby:directory:target/jpaserver_derby_files;create=true");
|
||||
retVal.setUsername("");
|
||||
retVal.setPassword("");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
|
||||
retVal.setPersistenceUnitName("HAPI_PU");
|
||||
retVal.setDataSource(dataSource());
|
||||
retVal.setPackagesToScan("ca.uhn.fhir.jpa.entity");
|
||||
retVal.setPersistenceProvider(new HibernatePersistenceProvider());
|
||||
retVal.setJpaProperties(jpaProperties());
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private Properties jpaProperties() {
|
||||
Properties extraProperties = new Properties();
|
||||
extraProperties.put("hibernate.dialect", org.hibernate.dialect.DerbyTenSevenDialect.class.getName());
|
||||
extraProperties.put("hibernate.format_sql", "true");
|
||||
extraProperties.put("hibernate.show_sql", "false");
|
||||
extraProperties.put("hibernate.hbm2ddl.auto", "update");
|
||||
extraProperties.put("hibernate.jdbc.batch_size", "20");
|
||||
extraProperties.put("hibernate.cache.use_query_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_second_level_cache", "false");
|
||||
extraProperties.put("hibernate.cache.use_structured_entries", "false");
|
||||
extraProperties.put("hibernate.cache.use_minimal_puts", "false");
|
||||
extraProperties.put("hibernate.search.default.directory_provider", "filesystem");
|
||||
extraProperties.put("hibernate.search.default.indexBase", "target/lucenefiles");
|
||||
extraProperties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
|
||||
return extraProperties;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Do some fancy logging to create a nice access log that has details about each incoming request.
|
||||
*/
|
||||
public IServerInterceptor loggingInterceptor() {
|
||||
LoggingInterceptor retVal = new LoggingInterceptor();
|
||||
retVal.setLoggerName("fhirtest.access");
|
||||
retVal.setMessageFormat(
|
||||
"Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
|
||||
retVal.setLogExceptions(true);
|
||||
retVal.setErrorMessageFormat("ERROR - ${requestVerb} ${requestUrl}");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* This interceptor adds some pretty syntax highlighting in responses when a browser is detected
|
||||
*/
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor responseHighlighterInterceptor() {
|
||||
ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean(autowire = Autowire.BY_TYPE)
|
||||
public IServerInterceptor subscriptionSecurityInterceptor() {
|
||||
SubscriptionsRequireManualActivationInterceptorDstu2 retVal = new SubscriptionsRequireManualActivationInterceptorDstu2();
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public IServerInterceptor webSocketSubscriptionDstu2Interceptor(){
|
||||
return new WebSocketSubscriptionDstu2Interceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public IServerInterceptor restHookSubscriptionDstu2Interceptor(){
|
||||
return new RestHookSubscriptionDstu2Interceptor();
|
||||
}
|
||||
}
|
|
@ -41,13 +41,13 @@ public class FhirTesterConfig {
|
|||
retVal
|
||||
.addServer()
|
||||
.withId("home")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU2)
|
||||
.withBaseUrl("${serverBase}/baseDstu2")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||
.withBaseUrl("${serverBase}/baseDstu3")
|
||||
.withName("Local Tester")
|
||||
.addServer()
|
||||
.withId("hapi")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU2)
|
||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu2")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu3")
|
||||
.withName("Public HAPI Test Server");
|
||||
return retVal;
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.to.FhirTesterMvcConfig;
|
||||
import ca.uhn.fhir.to.TesterConfig;
|
||||
|
||||
//@formatter:off
|
||||
/**
|
||||
* This spring config file configures the web testing module. It serves two
|
||||
* purposes:
|
||||
* 1. It imports FhirTesterMvcConfig, which is the spring config for the
|
||||
* tester itself
|
||||
* 2. It tells the tester which server(s) to talk to, via the testerConfig()
|
||||
* method below
|
||||
*/
|
||||
@Configuration
|
||||
@Import(FhirTesterMvcConfig.class)
|
||||
public class FhirTesterConfigDstu3 {
|
||||
|
||||
/**
|
||||
* This bean tells the testing webpage which servers it should configure itself
|
||||
* to communicate with. In this example we configure it to talk to the local
|
||||
* server, as well as one public server. If you are creating a project to
|
||||
* deploy somewhere else, you might choose to only put your own server's
|
||||
* address here.
|
||||
*
|
||||
* Note the use of the ${serverBase} variable below. This will be replaced with
|
||||
* the base URL as reported by the server itself. Often for a simple Tomcat
|
||||
* (or other container) installation, this will end up being something
|
||||
* like "http://localhost:8080/hapi-fhir-jpaserver-example". If you are
|
||||
* deploying your server to a place with a fully qualified domain name,
|
||||
* you might want to use that instead of using the variable.
|
||||
*/
|
||||
@Bean
|
||||
public TesterConfig testerConfig() {
|
||||
TesterConfig retVal = new TesterConfig();
|
||||
retVal
|
||||
.addServer()
|
||||
.withId("home")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||
.withBaseUrl("${serverBase}/baseDstu3")
|
||||
.withName("Local Tester")
|
||||
.addServer()
|
||||
.withId("hapi")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu3")
|
||||
.withName("Public HAPI Test Server");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
//@formatter:on
|
|
@ -1,16 +1,21 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
|
@ -21,19 +26,23 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
|
|||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
||||
|
@ -50,8 +59,7 @@ public class JpaServerDemo extends RestfulServer {
|
|||
* We want to support FHIR DSTU2 format. This means that the server
|
||||
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
|
||||
*
|
||||
* If you want to use DSTU1 instead, change the following line, and
|
||||
* change the 2 occurrences of dstu2 in web.xml to dstu1
|
||||
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
|
||||
*/
|
||||
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU3;
|
||||
setFhirContext(new FhirContext(fhirVersion));
|
||||
|
@ -104,13 +112,13 @@ public class JpaServerDemo extends RestfulServer {
|
|||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
IFhirSystemDao<Bundle, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
||||
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, Meta> systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
||||
IFhirSystemDao<Bundle, Meta> systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
|
@ -145,24 +153,6 @@ public class JpaServerDemo extends RestfulServer {
|
|||
*/
|
||||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Enable CORS
|
||||
*/
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
|
||||
config.addAllowedHeader("Origin");
|
||||
config.addAllowedHeader("Accept");
|
||||
config.addAllowedHeader("Prefer");
|
||||
config.addAllowedHeader("X-Requested-With");
|
||||
config.addAllowedHeader("Content-Type");
|
||||
config.addAllowedHeader("Access-Control-Request-Method");
|
||||
config.addAllowedHeader("Access-Control-Request-Headers");
|
||||
config.addAllowedOrigin("*");
|
||||
config.addExposedHeader("Location");
|
||||
config.addExposedHeader("Content-Location");
|
||||
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
|
||||
registerInterceptor(corsInterceptor);
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
*/
|
||||
|
|
|
@ -1,190 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
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;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemoDstu2 extends RestfulServer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private WebApplicationContext myAppCtx;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void initialize() throws ServletException {
|
||||
super.initialize();
|
||||
|
||||
/*
|
||||
* We want to support FHIR DSTU2 format. This means that the server
|
||||
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
|
||||
*
|
||||
* If you want to use DSTU1 instead, change the following line, and
|
||||
* change the 2 occurrences of dstu2 in web.xml to dstu1
|
||||
*/
|
||||
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU2;
|
||||
setFhirContext(new FhirContext(fhirVersion));
|
||||
|
||||
// Get the spring context from the web container (it's declared in web.xml)
|
||||
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
|
||||
|
||||
/*
|
||||
* The BaseJavaConfigDstu2.java class is a spring configuration
|
||||
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
|
||||
* contains bean definitions for a resource provider for each resource type
|
||||
*/
|
||||
String resourceProviderBeanName;
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu1";
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu2";
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu3";
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
|
||||
setResourceProviders(beans);
|
||||
|
||||
/*
|
||||
* The system provider implements non-resource-type methods, such as
|
||||
* transaction, and global history.
|
||||
*/
|
||||
Object systemProvider;
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
setPlainProviders(systemProvider);
|
||||
|
||||
/*
|
||||
* The conformance provider exports the supported resources, search parameters, etc for
|
||||
* this server. The JPA version adds resource counts to the exported statement, so it
|
||||
* is a nice addition.
|
||||
*/
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
IFhirSystemDao<List<IResource>, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu1 confProvider = new JpaConformanceProviderDstu1(this, systemDao);
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
IFhirSystemDao<Bundle, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
IFhirSystemDao<org.hl7.fhir.dstu3.model.Bundle, Meta> systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable ETag Support (this is already the default)
|
||||
*/
|
||||
setETagSupport(ETagSupportEnum.ENABLED);
|
||||
|
||||
/*
|
||||
* This server tries to dynamically generate narratives
|
||||
*/
|
||||
FhirContext ctx = getFhirContext();
|
||||
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||
|
||||
/*
|
||||
* Default to JSON and pretty printing
|
||||
*/
|
||||
setDefaultPrettyPrint(true);
|
||||
setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||
|
||||
/*
|
||||
* -- New in HAPI FHIR 1.5 --
|
||||
* This configures the server to page search results to and from
|
||||
* the database, instead of only paging them to memory. This may mean
|
||||
* a performance hit when performing searches that return lots of results,
|
||||
* but makes the server much more scalable.
|
||||
*/
|
||||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Enable CORS
|
||||
*/
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
|
||||
config.addAllowedHeader("Origin");
|
||||
config.addAllowedHeader("Accept");
|
||||
config.addAllowedHeader("Prefer");
|
||||
config.addAllowedHeader("X-Requested-With");
|
||||
config.addAllowedHeader("Content-Type");
|
||||
config.addAllowedHeader("Access-Control-Request-Method");
|
||||
config.addAllowedHeader("Access-Control-Request-Headers");
|
||||
config.addAllowedOrigin("*");
|
||||
config.addExposedHeader("Location");
|
||||
config.addExposedHeader("Content-Location");
|
||||
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
|
||||
registerInterceptor(corsInterceptor);
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
* figure out the FHIR base URL based on what the web container tells it, but
|
||||
* this doesn't always work. If you are setting links in your search bundles that
|
||||
* just refer to "localhost", you might want to use a server address strategy:
|
||||
*/
|
||||
//setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2"));
|
||||
|
||||
/*
|
||||
* If you are using DSTU3+, you may want to add a terminology uploader, which allows
|
||||
* uploading of external terminologies such as Snomed CT. Note that this uploader
|
||||
* does not have any security attached (any anonymous user may use it by default)
|
||||
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
|
||||
* with this feature.
|
||||
*/
|
||||
//if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
// registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
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;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu1;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemoDstu3 extends RestfulServer {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private WebApplicationContext myAppCtx;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected void initialize() throws ServletException {
|
||||
super.initialize();
|
||||
|
||||
/*
|
||||
* We want to support FHIR DSTU2 format. This means that the server
|
||||
* will use the DSTU2 bundle format and other DSTU2 encoding changes.
|
||||
*
|
||||
* If you want to use DSTU1 instead, change the following line, and change the 2 occurrences of dstu2 in web.xml to dstu1
|
||||
*/
|
||||
FhirVersionEnum fhirVersion = FhirVersionEnum.DSTU3;
|
||||
setFhirContext(new FhirContext(fhirVersion));
|
||||
|
||||
// Get the spring context from the web container (it's declared in web.xml)
|
||||
myAppCtx = ContextLoaderListener.getCurrentWebApplicationContext();
|
||||
|
||||
/*
|
||||
* The BaseJavaConfigDstu2.java class is a spring configuration
|
||||
* file which is automatically generated as a part of hapi-fhir-jpaserver-base and
|
||||
* contains bean definitions for a resource provider for each resource type
|
||||
*/
|
||||
String resourceProviderBeanName;
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu1";
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu2";
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
resourceProviderBeanName = "myResourceProvidersDstu3";
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
List<IResourceProvider> beans = myAppCtx.getBean(resourceProviderBeanName, List.class);
|
||||
setResourceProviders(beans);
|
||||
|
||||
/*
|
||||
* The system provider implements non-resource-type methods, such as
|
||||
* transaction, and global history.
|
||||
*/
|
||||
Object systemProvider;
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu1", JpaSystemProviderDstu1.class);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
systemProvider = myAppCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
setPlainProviders(systemProvider);
|
||||
|
||||
/*
|
||||
* The conformance provider exports the supported resources, search parameters, etc for
|
||||
* this server. The JPA version adds resource counts to the exported statement, so it
|
||||
* is a nice addition.
|
||||
*/
|
||||
if (fhirVersion == FhirVersionEnum.DSTU1) {
|
||||
IFhirSystemDao<List<IResource>, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu1", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu1 confProvider = new JpaConformanceProviderDstu1(this, systemDao);
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = myAppCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
IFhirSystemDao<Bundle, Meta> systemDao = myAppCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
||||
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao,
|
||||
myAppCtx.getBean(DaoConfig.class));
|
||||
confProvider.setImplementationDescription("Example Server");
|
||||
setServerConformanceProvider(confProvider);
|
||||
} else {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable ETag Support (this is already the default)
|
||||
*/
|
||||
setETagSupport(ETagSupportEnum.ENABLED);
|
||||
|
||||
/*
|
||||
* This server tries to dynamically generate narratives
|
||||
*/
|
||||
FhirContext ctx = getFhirContext();
|
||||
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||
|
||||
/*
|
||||
* Default to JSON and pretty printing
|
||||
*/
|
||||
setDefaultPrettyPrint(true);
|
||||
setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||
|
||||
/*
|
||||
* -- New in HAPI FHIR 1.5 --
|
||||
* This configures the server to page search results to and from
|
||||
* the database, instead of only paging them to memory. This may mean
|
||||
* a performance hit when performing searches that return lots of results,
|
||||
* but makes the server much more scalable.
|
||||
*/
|
||||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
* figure out the FHIR base URL based on what the web container tells it, but
|
||||
* this doesn't always work. If you are setting links in your search bundles that
|
||||
* just refer to "localhost", you might want to use a server address strategy:
|
||||
*/
|
||||
//setServerAddressStrategy(new HardcodedServerAddressStrategy("http://mydomain.com/fhir/baseDstu2"));
|
||||
|
||||
/*
|
||||
* If you are using DSTU3+, you may want to add a terminology uploader, which allows
|
||||
* uploading of external terminologies such as Snomed CT. Note that this uploader
|
||||
* does not have any security attached (any anonymous user may use it by default)
|
||||
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
|
||||
* with this feature.
|
||||
*/
|
||||
//if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||
// registerProvider(myAppCtx.getBean(TerminologyUploaderProviderDstu3.class));
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
|
@ -28,14 +28,14 @@
|
|||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu3</param-value>
|
||||
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>2</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemoDstu3</servlet-class>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemo</servlet-class>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3WSocket
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfig
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
|
@ -28,14 +28,14 @@
|
|||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu3</param-value>
|
||||
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfig</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>2</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemoDstu3</servlet-class>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemo</servlet-class>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
|
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
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.valueset.ObservationStatusEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionStatusEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3IT for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createSubscription' test
|
||||
* 2. Update the subscription id in the 'attachWebSocket' test
|
||||
* 3. Execute the 'attachWebSocket' test
|
||||
* 4. Execute the 'sendObservation' test
|
||||
* 5. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
@Ignore
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu2IT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu2IT.class);
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu2Client();
|
||||
|
||||
@Test
|
||||
public void clean() {
|
||||
RemoveDstu2TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu2TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSubscription() {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
String criteria = "Observation?code=SNOMED-CT|82313006&_format=xml";
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Subscription.Channel channel = new Subscription.Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(subscription).execute();
|
||||
String id = methodOutcome.getId().getIdPart();
|
||||
|
||||
System.out.println("Subscription id generated by server is: " + id);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void attachWebSocket() throws Exception {
|
||||
String subscriptionId = "1";
|
||||
subscriptionId = subscriptionId + "";
|
||||
|
||||
String target = "ws://localhost:9092/websocket/dstu2";
|
||||
|
||||
WebSocketClient webSocketClient = new WebSocketClient();
|
||||
SocketImplementation socket = new SocketImplementation(subscriptionId, EncodingEnum.JSON);
|
||||
|
||||
try {
|
||||
webSocketClient.start();
|
||||
URI echoUri = new URI(target);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
webSocketClient.connect(socket, echoUri, request);
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(500L);
|
||||
}
|
||||
|
||||
} finally {
|
||||
try {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
webSocketClient.stop();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
|
||||
MethodOutcome methodOutcome2 = client.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
System.out.println("Observation id generated by server is: " + observationId);
|
||||
}
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
* subscription
|
||||
* <p>
|
||||
* Note: This test only returns a ping with the subscription id, Check FhirSubscriptionWithSubscriptionIdDstu3IT for
|
||||
* a test that returns the xml of the observation
|
||||
* <p>
|
||||
* To execute the following test, execute it the following way:
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'createPatient' test
|
||||
* 2. Update the patient id static variable
|
||||
* 3. Execute the 'createSubscription' test
|
||||
* 4. Update the subscription id static variable
|
||||
* 5. Execute the 'attachWebSocket' test
|
||||
* 6. Execute the 'sendObservation' test
|
||||
* 7. Look in the 'attachWebSocket' terminal execution and wait for your ping with the subscription id
|
||||
*/
|
||||
@Ignore
|
||||
public class FhirSubscriptionWithSubscriptionIdDstu3IT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
|
||||
public static final String WEBSOCKET_LISTENER_URL = "ws://localhost:9093/websocket/dstu3";
|
||||
|
||||
public static final String PATIENT_ID = "5102";
|
||||
public static final String SUBSCRIPTION_ID = "5103";
|
||||
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
@Test
|
||||
public void clean() {
|
||||
RemoveDstu3TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu3TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createPatient() throws Exception {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
Patient patient = FhirDstu3Util.getPatient();
|
||||
MethodOutcome methodOutcome = client.create().resource(patient).execute();
|
||||
String id = methodOutcome.getId().getIdPart();
|
||||
patient.setId(id);
|
||||
System.out.println("Patient id generated by server is: " + id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSubscription() {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
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("Observation?subject=Patient/" + PATIENT_ID);
|
||||
subscription.setCriteria("Observation?code=SNOMED-CT|82313006");
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
channel.setPayload("application/json");
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(subscription).execute();
|
||||
String id = methodOutcome.getId().getIdPart();
|
||||
|
||||
System.out.println("Subscription id generated by server is: " + id);
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
public void attachWebSocket() throws Exception {
|
||||
WebSocketClient webSocketClient = new WebSocketClient();
|
||||
SocketImplementation socket = new SocketImplementation(SUBSCRIPTION_ID, EncodingEnum.JSON);
|
||||
|
||||
try {
|
||||
webSocketClient.start();
|
||||
URI echoUri = new URI(WEBSOCKET_LISTENER_URL);
|
||||
ClientUpgradeRequest request = new ClientUpgradeRequest();
|
||||
ourLog.info("Connecting to : {}", echoUri);
|
||||
webSocketClient.connect(socket, echoUri, request);
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(500L);
|
||||
}
|
||||
|
||||
} finally {
|
||||
try {
|
||||
ourLog.info("Shutting down websocket client");
|
||||
webSocketClient.stop();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failure", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservation() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
Reference reference = new Reference();
|
||||
reference.setReference("Patient/" + PATIENT_ID);
|
||||
observation.setSubject(reference);
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
MethodOutcome methodOutcome2 = client.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
System.out.println("Observation id generated by server is: " + observationId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createObservationThatDoesNotMatch() throws Exception {
|
||||
Observation observation = new Observation();
|
||||
IdDt idDt = new IdDt();
|
||||
idDt.setValue("Patient/" + PATIENT_ID);
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("8231");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
MethodOutcome methodOutcome2 = client.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
System.out.println("Observation id generated by server is: " + observationId);
|
||||
|
||||
}
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
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.valueset.ObservationStatusEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription by creating a websocket that includes the subscription criteria. The server will create
|
||||
* a subscription automatically and return the subscription id
|
||||
* <p>
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'attachWebSocket' test
|
||||
* 2. Execute the 'sendObservation' test
|
||||
* 3. Look in the 'attachWebSocket' terminal execution and wait for your JSON/XML response
|
||||
*/
|
||||
@Ignore
|
||||
public class FhirSubscriptionWithWebsocketCriteriaDstu2IT {
|
||||
|
||||
public final static String PORT = "9092";
|
||||
public final static String WEBSOCKET_PATH = "/websocket/dstu2";
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu2Client();
|
||||
|
||||
@Test
|
||||
public void clean() {
|
||||
RemoveDstu2TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu2TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
/**
|
||||
* Attach a websocket to the FHIR server based on a criteria
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void attachWebSocket() throws Exception {
|
||||
String criteria = "Observation?code=SNOMED-CT|82313006&_format=xml";
|
||||
|
||||
SocketImplementation socket = new SocketImplementation(criteria, EncodingEnum.JSON);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + PORT + WEBSOCKET_PATH);
|
||||
client.connect(socket, echoUri);
|
||||
|
||||
Thread.sleep(5000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(60000);
|
||||
socket.keepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an observation in the FHIR server
|
||||
*/
|
||||
@Test
|
||||
public void createObservation() {
|
||||
Observation observation = new Observation();
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
|
||||
MethodOutcome methodOutcome2 = client.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
System.out.println("Observation id generated by server is: " + observationId);
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import org.eclipse.jetty.websocket.client.WebSocketClient;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription by creating a websocket that includes the subscription criteria. The server will create
|
||||
* a subscription automatically and return the subscription id
|
||||
* <p>
|
||||
* 0. execute 'clean' test
|
||||
* 1. Execute the 'attachWebSocket' test
|
||||
* 2. Execute the 'sendObservation' test
|
||||
* 3. Look in the 'attachWebSocket' terminal execution and wait for your JSON/XML response
|
||||
*/
|
||||
@Ignore
|
||||
public class FhirSubscriptionWithWebsocketCriteriaDstu3IT {
|
||||
|
||||
public final static String PORT = "9093";
|
||||
public final static String WEBSOCKET_PATH = "/websocket/dstu3";
|
||||
//public final static String WEBSOCKET_PATH = "/baseDstu3";
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
@Test
|
||||
public void clean() {
|
||||
RemoveDstu3TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu3TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a websocket to the FHIR server based on a criteria
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void attachWebSocket() throws Exception {
|
||||
String criteria = "Observation?_format=xml";
|
||||
// String criteria = "Observation?code=SNOMED-CT|82313006&_format=xml";
|
||||
SocketImplementation socket = new SocketImplementation(criteria, EncodingEnum.JSON);
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
|
||||
try {
|
||||
client.start();
|
||||
URI echoUri = new URI("ws://localhost:" + PORT + WEBSOCKET_PATH);
|
||||
|
||||
client.connect(socket, echoUri);
|
||||
|
||||
Thread.sleep(5000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Thread.sleep(60000);
|
||||
|
||||
socket.keepAlive();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an observation in the FHIR server
|
||||
*/
|
||||
@Test
|
||||
public void createObservation() {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
Observation observation = new Observation();
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode("82313006");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
|
||||
MethodOutcome methodOutcome2 = client.create().resource(observation).execute();
|
||||
String observationId = methodOutcome2.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
System.out.println("Observation id generated by server is: " + observationId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteObservation(){
|
||||
String observationId = "5103";
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
client.delete().resourceById(Observation.class.getSimpleName(), observationId).execute();
|
||||
}
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import org.hl7.fhir.dstu3.model.CodeableConcept;
|
||||
import org.hl7.fhir.dstu3.model.Coding;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
@Ignore
|
||||
public class RestHookTestDstu3IT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
@Before
|
||||
public void clean() {
|
||||
RemoveDstu3TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu3TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscription() {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
|
||||
String payload = "application/json";
|
||||
String endpoint = "http://localhost:10080/rest-hook";
|
||||
|
||||
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, endpoint, client);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, endpoint, client);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT", client);
|
||||
//Should see only one subscription notification
|
||||
|
||||
Subscription subscriptionTemp = client.read(Subscription.class, subscription2.getId());
|
||||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
||||
subscriptionTemp.setCriteria(criteria1);
|
||||
client.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
|
||||
|
||||
Observation observation2 = sendObservation(code, "SNOMED-CT", client);
|
||||
//Should see two subscription notifications
|
||||
|
||||
client.delete().resourceById(new IdDt("Subscription", subscription2.getId())).execute();
|
||||
|
||||
Observation observationTemp3 = sendObservation(code, "SNOMED-CT", client);
|
||||
//Should see only one subscription notification
|
||||
|
||||
Observation observation3 = client.read(Observation.class, observationTemp3.getId());
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation3.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode(code + "111");
|
||||
coding.setSystem("SNOMED-CT");
|
||||
client.update().resource(observation3).withId(observation3.getIdElement()).execute();
|
||||
//Should see no subscription notification
|
||||
|
||||
Observation observation3a = client.read(Observation.class, observationTemp3.getId());
|
||||
|
||||
CodeableConcept codeableConcept1 = new CodeableConcept();
|
||||
observation3a.setCode(codeableConcept1);
|
||||
Coding coding1 = codeableConcept1.addCoding();
|
||||
coding1.setCode(code);
|
||||
coding1.setSystem("SNOMED-CT");
|
||||
client.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
|
||||
//Should see only one subscription notification
|
||||
|
||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
||||
Assert.assertFalse(observation1.getId().isEmpty());
|
||||
Assert.assertFalse(observation2.getId().isEmpty());
|
||||
}
|
||||
|
||||
public Subscription createSubscription(String criteria, String payload, String endpoint, IGenericClient client) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
|
||||
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
|
||||
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
channel.setPayload(payload);
|
||||
channel.setEndpoint(endpoint);
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(subscription).execute();
|
||||
subscription.setId(methodOutcome.getId().getIdPart());
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public Observation sendObservation(String code, String system, IGenericClient client) {
|
||||
Observation observation = new Observation();
|
||||
CodeableConcept codeableConcept = new CodeableConcept();
|
||||
observation.setCode(codeableConcept);
|
||||
Coding coding = codeableConcept.addCoding();
|
||||
coding.setCode(code);
|
||||
coding.setSystem(system);
|
||||
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(observation).execute();
|
||||
|
||||
String observationId = methodOutcome.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
return observation;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Copyright 2017 Cognitive Medical Systems, Inc (http://www.cognitivemedicine.com).
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @author Jeff Chung
|
||||
*/
|
||||
|
||||
package ca.uhn.fhir.jpa.demo.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@WebSocket
|
||||
public class SocketImplementation {
|
||||
|
||||
private String myCriteria;
|
||||
private Session session;
|
||||
|
||||
protected String myError;
|
||||
protected boolean myGotBound;
|
||||
protected int myPingCount;
|
||||
protected String mySubsId;
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(SocketImplementation.class);
|
||||
|
||||
public SocketImplementation(String theCriteria, EncodingEnum theEncoding) {
|
||||
myCriteria = theCriteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is executed when the client is connecting to the server.
|
||||
* In this case, we are sending a message to create the subscription dynamiclly
|
||||
*
|
||||
* @param session
|
||||
*/
|
||||
@OnWebSocketConnect
|
||||
public void onConnect(Session session) {
|
||||
ourLog.info("Got connect: {}", session);
|
||||
this.session = session;
|
||||
try {
|
||||
String sending = "bind " + myCriteria;
|
||||
ourLog.info("Sending: {}", sending);
|
||||
session.getRemote().sendString(sending);
|
||||
|
||||
ourLog.info("Connection: DONE");
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
ourLog.error("Failure", t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the message handler for the client
|
||||
*
|
||||
* @param theMsg
|
||||
*/
|
||||
@OnWebSocketMessage
|
||||
public void onMessage(String theMsg) {
|
||||
ourLog.info("Got msg: " + theMsg);
|
||||
|
||||
if (theMsg.startsWith("bound ")) {
|
||||
myGotBound = true;
|
||||
mySubsId = (theMsg.substring("bound ".length()));
|
||||
myPingCount++;
|
||||
} else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) {
|
||||
String text = theMsg.substring(("add " + mySubsId + "\n").length());
|
||||
ourLog.info("text: " + text);
|
||||
myPingCount++;
|
||||
} else {
|
||||
myError = "Unexpected message: " + theMsg;
|
||||
}
|
||||
}
|
||||
|
||||
public void keepAlive() {
|
||||
if (this.session != null) {
|
||||
try {
|
||||
session.getRemote().sendString("keep alive");
|
||||
} catch (Throwable t) {
|
||||
ourLog.error("Failure", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue