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:
James 2017-05-21 15:28:08 -04:00
parent 11eeeedf0d
commit a13247ad4b
51 changed files with 2560 additions and 2882 deletions

View File

@ -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>

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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()) {

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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,6 +65,7 @@ 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;
@ -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());

View File

@ -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) {
private void checkSubscriptions(IIdType idType, String resourceType, RestOperationTypeEnum theOperation) {
for (Subscription subscription : myRestHookSubscriptions) {
// see if the criteria matches the created object
logger.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
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
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
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);
}
String resourceName = myCtx.getResourceDefinition(theResource).getName();
String payload = theSubscription.getChannel().getPayload();
String resourceId = theResource.getIdElement().getIdPart();
// 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);
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);
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);
} else {
logger.warn("Unsupported payload " + payload + ". Returning an empty notification");
request = new HttpPost(url);
request = putRequest;
}
// 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;
}
}

View File

@ -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,178 +44,73 @@ 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());
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
@ -245,102 +127,75 @@ public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter imp
// 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 = theSubscription.getChannel().getPayload();
String resourceId = theResource.getIdElement().getIdPart();
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);
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);
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);
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;
}

View File

@ -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
}
}

View File

@ -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();
}
}

View File

@ -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));

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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() {

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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>

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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)
*/

View File

@ -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));
//}
}
}

View File

@ -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));
//}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}
}