mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-07 05:28:21 +00:00
Resthook subscription implementation, event driven websocket subscription implementation, Tminus subscription support, notification on delete
This commit is contained in:
parent
e53d747f2b
commit
663125fe94
@ -28,6 +28,7 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
@ -49,6 +50,8 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
|
||||
private final FhirVersionEnum myStructureVersion;
|
||||
private volatile RuntimeResourceDefinition myBaseDefinition;
|
||||
|
||||
|
||||
|
||||
public RuntimeResourceDefinition(FhirContext theContext, String theResourceName, Class<? extends IBaseResource> theClass, ResourceDef theResourceAnnotation, boolean theStandardType, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
|
||||
super(theResourceName, theClass, theStandardType, theContext, theClassToElementDefinitions);
|
||||
myContext = theContext;
|
||||
@ -68,6 +71,7 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void addSearchParam(RuntimeSearchParam theParam) {
|
||||
myNameToSearchParam.put(theParam.getName(), theParam);
|
||||
}
|
||||
|
@ -14,6 +14,28 @@
|
||||
<name>HAPI FHIR JPA Server</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jaxrs</groupId>
|
||||
<artifactId>jackson-jaxrs-json-provider</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-hibernate4</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
<version>9.5.1-5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-csv</artifactId>
|
||||
@ -363,6 +385,7 @@
|
||||
|
||||
<properties>
|
||||
<skip-hib4>false</skip-hib4>
|
||||
<jackson.version>2.7.1</jackson.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
|
@ -886,6 +886,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
||||
|
||||
if (theRequestDetails.isSubRequest()) {
|
||||
theParams.setLoadSynchronous(true);
|
||||
int max = myDaoConfig.getMaximumSearchResultCountInTransaction();
|
||||
theParams.setLoadSynchronousUpTo(myDaoConfig.getMaximumSearchResultCountInTransaction());
|
||||
}
|
||||
|
||||
|
@ -180,7 +180,7 @@ public class DaoConfig {
|
||||
@Deprecated
|
||||
public List<IServerInterceptor> getInterceptors() {
|
||||
if (myInterceptors == null) {
|
||||
return Collections.emptyList();
|
||||
myInterceptors = new ArrayList<IServerInterceptor>();
|
||||
}
|
||||
return myInterceptors;
|
||||
}
|
||||
|
@ -41,6 +41,11 @@ import ca.uhn.fhir.util.FhirTerser;
|
||||
|
||||
public class FhirResourceDaoDstu1<T extends IResource> extends BaseHapiFhirResourceDao<T> {
|
||||
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> getIncludeValues(FhirTerser t, Include next, IBaseResource nextResource, RuntimeResourceDefinition def) {
|
||||
List<Object> values;
|
||||
|
@ -145,6 +145,11 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class IdChecker implements IValidatorModule {
|
||||
|
||||
private ValidationModeEnum myMode;
|
||||
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||
|
||||
import javax.persistence.Query;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.valueset.SubscriptionChannelTypeEnum;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
@ -116,8 +117,12 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||
}
|
||||
|
||||
@Override
|
||||
public int pollForNewUndeliveredResources() {
|
||||
return pollForNewUndeliveredResources((String) null);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public synchronized int pollForNewUndeliveredResources() {
|
||||
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return 0;
|
||||
}
|
||||
@ -141,7 +146,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -149,8 +154,20 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) {
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
|
||||
if (!subscription.getChannel().getType().equals(SubscriptionChannelTypeEnum.WEBSOCKET.getCode())){
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
return 0;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
|
||||
@ -297,7 +314,7 @@ public class FhirResourceDaoSubscriptionDstu2 extends FhirResourceDaoDstu2<Subsc
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
|
||||
String query = theResource.getCriteria();
|
||||
if (isBlank(query)) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria must be populated");
|
||||
|
@ -25,6 +25,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import org.hl7.fhir.dstu3.model.CodeSystem;
|
||||
import org.hl7.fhir.dstu3.model.Resource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
@ -243,6 +244,10 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
||||
*/
|
||||
MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequestDetails);
|
||||
|
||||
RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria);
|
||||
|
||||
<R extends IBaseResource> IFhirResourceDao<R> getDao(Class<R> theType) ;
|
||||
|
||||
// /**
|
||||
// * Invoke the everything operation
|
||||
// */
|
||||
|
@ -37,4 +37,5 @@ public interface IFhirResourceDaoSubscription<T extends IBaseResource> extends I
|
||||
|
||||
void pollForNewUndeliveredResourcesScheduler();
|
||||
|
||||
int pollForNewUndeliveredResources(String resourceType);
|
||||
}
|
||||
|
@ -1474,7 +1474,8 @@ public class SearchBuilder implements ISearchBuilder {
|
||||
public void loadResourcesByPid(Collection<Long> theIncludePids, List<IBaseResource> theResourceListToPopulate, Set<Long> theRevIncludedPids, boolean theForHistoryOperation,
|
||||
EntityManager entityManager, FhirContext context, IDao theDao) {
|
||||
if (theIncludePids.isEmpty()) {
|
||||
return;
|
||||
ourLog.info("The include pids are empty");
|
||||
//return;
|
||||
}
|
||||
|
||||
// Dupes will cause a crash later anyhow, but this is expensive so only do it
|
||||
|
@ -26,6 +26,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
|
||||
@ -71,6 +72,9 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
|
||||
@Qualifier("myInstanceValidatorDstu3")
|
||||
private IValidatorModule myInstanceValidator;
|
||||
|
||||
@Autowired
|
||||
private FhirContext fhirContext;
|
||||
|
||||
@Override
|
||||
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) {
|
||||
OperationOutcome oo = new OperationOutcome();
|
||||
@ -163,6 +167,26 @@ 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;
|
||||
|
@ -119,9 +119,13 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public int pollForNewUndeliveredResources() {
|
||||
return pollForNewUndeliveredResources((String) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.NOT_SUPPORTED)
|
||||
public synchronized int pollForNewUndeliveredResources() {
|
||||
public synchronized int pollForNewUndeliveredResources(final String resourceType) {
|
||||
if (getConfig().isSubscriptionEnabled() == false) {
|
||||
return 0;
|
||||
}
|
||||
@ -145,7 +149,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||
@Override
|
||||
public Integer doInTransaction(TransactionStatus theStatus) {
|
||||
SubscriptionTable nextSubscriptionTable = mySubscriptionTableDao.findOne(nextSubscriptionTablePid);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable);
|
||||
return pollForNewUndeliveredResources(nextSubscriptionTable, resourceType);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -153,8 +157,19 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable) {
|
||||
private int pollForNewUndeliveredResources(SubscriptionTable theSubscriptionTable, String resourceType) {
|
||||
Subscription subscription = toResource(Subscription.class, theSubscriptionTable.getSubscriptionResource(), false);
|
||||
if (subscription.getChannel().getType() != Subscription.SubscriptionChannelType.WEBSOCKET){
|
||||
ourLog.info("Skipping non web socket subscription");
|
||||
return 0;
|
||||
}
|
||||
|
||||
ourLog.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
if (resourceType != null && subscription.getCriteria() != null && !subscription.getCriteria().startsWith(resourceType)) {
|
||||
ourLog.info("Skipping subscription search for " + resourceType + " because it does not match the criteria " + subscription.getCriteria());
|
||||
return 0;
|
||||
}
|
||||
|
||||
RuntimeResourceDefinition resourceDef = validateCriteriaAndReturnResourceDefinition(subscription);
|
||||
SearchParameterMap criteriaUrl = translateMatchUrl(this, getContext(), subscription.getCriteria(), resourceDef);
|
||||
|
||||
@ -302,7 +317,7 @@ public class FhirResourceDaoSubscriptionDstu3 extends FhirResourceDaoDstu3<Subsc
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
|
||||
public RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(Subscription theResource) {
|
||||
String query = theResource.getCriteria();
|
||||
if (isBlank(query)) {
|
||||
throw new UnprocessableEntityException("Subscription.criteria must be populated");
|
||||
|
@ -0,0 +1,429 @@
|
||||
/*
|
||||
* 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.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 org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
@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
|
||||
*/
|
||||
private void checkSubscriptions(IIdType idType, String resourceType) {
|
||||
for (Subscription subscription : restHookSubscriptions) {
|
||||
//see if the criteria matches the created object
|
||||
logger.info("subscription for " + resourceType + " with criteria " + subscription.getCriteria());
|
||||
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());
|
||||
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();
|
||||
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);
|
||||
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();
|
||||
HttpUriRequest request = null;
|
||||
String payload = subscription.getChannel().getPayload();
|
||||
//HTTP post
|
||||
if (payload == null || payload.trim().length() == 0) {
|
||||
//return an empty response as there is no payload
|
||||
logger.info("No payload found, returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
//HTTP put
|
||||
else if (payload.equals("application/xml") || payload.equals("application/fhir+xml")) {
|
||||
logger.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
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);
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.warn("Unsupported payload " + payload + ". Returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
|
||||
//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
|
||||
*
|
||||
* @param criteria
|
||||
* @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);
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IFhirResourceDao<? extends IBaseResource> responseDao = myResourceSubscriptionDao.getDao(responseResourceDef.getImplementingClass());
|
||||
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
|
||||
return responseResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bundle to return to the client
|
||||
*
|
||||
* @param resourcelist
|
||||
* @return
|
||||
*/
|
||||
private Bundle createBundle(List<IBaseResource> resourcelist) {
|
||||
Bundle bundle = new Bundle();
|
||||
for (IBaseResource resource : resourcelist) {
|
||||
Bundle.Entry entry = bundle.addEntry();
|
||||
entry.setResource((IResource) resource);
|
||||
}
|
||||
bundle.setTotal(resourcelist.size());
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a resource into a string entity
|
||||
*
|
||||
* @param encoding
|
||||
* @param anyResource
|
||||
* @return
|
||||
*/
|
||||
private StringEntity getStringEntity(EncodingEnum encoding, IResource 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription from cache
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isNotifyOnDelete() {
|
||||
return notifyOnDelete;
|
||||
}
|
||||
|
||||
public void setNotifyOnDelete(boolean notifyOnDelete) {
|
||||
this.notifyOnDelete = notifyOnDelete;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,446 @@
|
||||
/*
|
||||
* 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.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 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 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;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
|
||||
public class RestHookSubscriptionDstu3Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu3")
|
||||
private IFhirResourceDao<Subscription> mySubscriptionDao;
|
||||
@Autowired
|
||||
@Qualifier("myObservationDaoDstu3")
|
||||
private IFhirResourceDao<Observation> myObservationDao;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check subscriptions and send notifications or payload
|
||||
*
|
||||
* @param idType
|
||||
* @param resourceType
|
||||
*/
|
||||
private void checkSubscriptions(IIdType idType, String resourceType) {
|
||||
/*
|
||||
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());
|
||||
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());
|
||||
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;
|
||||
}
|
||||
|
||||
//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);
|
||||
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();
|
||||
HttpUriRequest request = null;
|
||||
|
||||
String payload = subscription.getChannel().getPayload();
|
||||
//HTTP post
|
||||
if (payload == null || payload.trim().length() == 0) {
|
||||
//return an empty response as there is no payload
|
||||
logger.info("No payload found, returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
//HTTP put
|
||||
else if (EncodingEnum.XML.equals(EncodingEnum.forContentType(payload))) {
|
||||
logger.info("XML payload found");
|
||||
StringEntity entity = getStringEntity(EncodingEnum.XML, resource);
|
||||
HttpPut putRequest = new HttpPut(url);
|
||||
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);
|
||||
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);
|
||||
|
||||
//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;
|
||||
}
|
||||
} else {
|
||||
logger.warn("Unsupported payload " + payload + ". Returning an empty notification");
|
||||
request = new HttpPost(url);
|
||||
}
|
||||
|
||||
//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
|
||||
*
|
||||
* @param criteria
|
||||
* @return
|
||||
*/
|
||||
private IBundleProvider getBundleProvider(String criteria) {
|
||||
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 = mySubscriptionDao.getDao(responseResourceDef.getImplementingClass());
|
||||
IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
|
||||
return responseResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a bundle to return to the client
|
||||
*
|
||||
* @param resourcelist
|
||||
* @return
|
||||
*/
|
||||
private Bundle createBundle(List<IBaseResource> resourcelist) {
|
||||
Bundle bundle = new Bundle();
|
||||
for (IBaseResource resource : resourcelist) {
|
||||
Bundle.BundleEntryComponent entry = bundle.addEntry();
|
||||
entry.setResource((Resource) resource);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription from cache
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
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.getIdElement().getIdPart())) {
|
||||
return restHookSubscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isNotifyOnDelete() {
|
||||
return notifyOnDelete;
|
||||
}
|
||||
|
||||
public void setNotifyOnDelete(boolean notifyOnDelete) {
|
||||
this.notifyOnDelete = notifyOnDelete;
|
||||
}
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.interceptor;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||
import ca.uhn.fhir.rest.method.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
public class WebSocketSubscriptionDstu2Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketSubscriptionDstu2Interceptor.class);
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu2")
|
||||
private IFhirResourceDao<Subscription> reference;
|
||||
|
||||
private IFhirResourceDaoSubscription<Subscription> casted;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct(){
|
||||
casted = (IFhirResourceDaoSubscription) reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theResponseObject
|
||||
* The actual object which is being streamed to the client as a response
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||
if (theRequestDetails.getResourceName() == null ||
|
||||
theRequestDetails.getResourceName().isEmpty() ||
|
||||
theRequestDetails.getResourceName().equals("Subscription")) {
|
||||
return super.outgoingResponse(theRequestDetails, theResponseObject);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
return super.outgoingResponse(theRequestDetails, theResponseObject);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.interceptor;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||
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.InterceptorAdapter;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
|
||||
public class WebSocketSubscriptionDstu3Interceptor extends InterceptorAdapter implements IJpaServerInterceptor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketSubscriptionDstu3Interceptor.class);
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoDstu3")
|
||||
private IFhirResourceDao<Subscription> reference;
|
||||
|
||||
private IFhirResourceDaoSubscription<Subscription> casted;
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct(){
|
||||
casted = (IFhirResourceDaoSubscription) reference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(ActionRequestDetails theDetails, ResourceTable theResourceTable) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||
* @param theResponseObject
|
||||
* The actual object which is being streamed to the client as a response
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
|
||||
if (theRequestDetails.getResourceName() == null ||
|
||||
theRequestDetails.getResourceName().isEmpty() ||
|
||||
theRequestDetails.getResourceName().equals("Subscription")) {
|
||||
return super.outgoingResponse(theRequestDetails, theResponseObject);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
return super.incomingRequestPostProcessed(theRequestDetails, theRequest, theResponse);
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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.service;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TMinusService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TMinusService.class);
|
||||
|
||||
private static final String TMINUS = "Tminus";
|
||||
private static final String WEEK = "w";
|
||||
private static final String DAY = "d";
|
||||
private static final String HOUR = "h";
|
||||
private static final String MINUTE = "m";
|
||||
private static final String SECOND = "s";
|
||||
|
||||
private static final long MINUTE_AS_MILLIS = 60 * 1000;
|
||||
private static final long HOUR_AS_MILLIS = 60 * 60 * 1000;
|
||||
private static final long DAY_AS_MILLIS = 24 * 60 * 60 * 1000;
|
||||
private static final long WEEK_AS_MILLIS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
private static final Pattern TMINUS_PATTERN_REGEX = Pattern.compile("&([a-zA-Z0-9_]+)=Tminus([0-9]+)([wdhms])");
|
||||
|
||||
public static void main(String ... aaa){
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&effectiveDate=Tminus1m" + "&_format=xml";
|
||||
|
||||
Pattern myPattern = TMINUS_PATTERN_REGEX;
|
||||
Matcher matcher = myPattern.matcher(payloadCriteria);
|
||||
|
||||
if (matcher.find()) {
|
||||
String tMinus = matcher.group();
|
||||
String tMinusVarName = tMinus.substring(1, tMinus.indexOf("="));
|
||||
String tMinusValue = tMinus.substring(tMinus.indexOf(TMINUS) + TMINUS.length(), tMinus.length() - 1);
|
||||
String tMinusPeriod = tMinus.substring(tMinus.length() - 1);
|
||||
|
||||
System.out.println(matcher.group());
|
||||
System.out.println(tMinusVarName);
|
||||
System.out.println(tMinusValue);
|
||||
System.out.println(tMinusPeriod);
|
||||
}else{
|
||||
System.out.println("mmm");
|
||||
}
|
||||
}
|
||||
|
||||
public static String parseCriteria(String criteria) {
|
||||
Matcher matcher = TMINUS_PATTERN_REGEX.matcher(criteria);
|
||||
String response = criteria;
|
||||
boolean matcherFound = false;
|
||||
Date currentDate = new Date();
|
||||
|
||||
|
||||
while (matcher.find()) {
|
||||
matcherFound = true;
|
||||
|
||||
String tMinus = matcher.group();
|
||||
String tMinusVarName = tMinus.substring(1, tMinus.indexOf("="));
|
||||
String tMinusValue = tMinus.substring(tMinus.indexOf(TMINUS) + TMINUS.length(), tMinus.length() - 1);
|
||||
String tMinusPeriod = tMinus.substring(tMinus.length() - 1);
|
||||
long tMinusMillis = getTMinusValueAsLong(tMinusValue, tMinusPeriod);
|
||||
String dateValue = getDateParameterValue(tMinusMillis, tMinusVarName, currentDate);
|
||||
|
||||
logger.debug("Tminus value replaced in criteria: " + criteria);
|
||||
response = response.replace(tMinus, dateValue);
|
||||
}
|
||||
|
||||
if(!matcherFound){
|
||||
logger.debug("Tminus value not found in criteria: " + criteria);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private static String getDateParameterValue(long tMinusMillis, String tMinusVarName, Date currentDate){
|
||||
Date lowerDate = new Date(currentDate.getTime() - tMinusMillis);
|
||||
|
||||
DateTimeType lowerDateTimeType = new DateTimeType(lowerDate);
|
||||
DateTimeType currentDateTimeType = new DateTimeType(currentDate);
|
||||
|
||||
return "&" + tMinusVarName + "=%3E%3D" + lowerDateTimeType.getValueAsString() + "&" + tMinusVarName + "=%3C%3D" + currentDateTimeType.getValueAsString();
|
||||
}
|
||||
|
||||
private static long getTMinusValueAsLong(String tMinusValue, String tMinusPeriod) {
|
||||
long tMinusLongValue = Long.parseLong(tMinusValue);
|
||||
long tMinusMillis;
|
||||
|
||||
if (WEEK.equals(tMinusPeriod)) {
|
||||
tMinusMillis = WEEK_AS_MILLIS * tMinusLongValue;
|
||||
} else if (DAY.equals(tMinusPeriod)) {
|
||||
tMinusMillis = DAY_AS_MILLIS * tMinusLongValue;
|
||||
} else if (HOUR.equals(tMinusPeriod)) {
|
||||
tMinusMillis = HOUR_AS_MILLIS * tMinusLongValue;
|
||||
} else if (MINUTE.equals(tMinusPeriod)) {
|
||||
tMinusMillis = MINUTE_AS_MILLIS * tMinusLongValue;
|
||||
} else if (SECOND.equals(tMinusPeriod)) {
|
||||
tMinusMillis = 1000 * tMinusLongValue;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Period not recognized: " + tMinusPeriod);
|
||||
}
|
||||
|
||||
return tMinusMillis;
|
||||
}
|
||||
}
|
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
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.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
public class SubscriptionWebsocketReturnResourceHandlerDstu2 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerDstu2.class);
|
||||
|
||||
@Autowired
|
||||
@Qualifier("myFhirContextDstu2")
|
||||
private FhirContext myCtx;
|
||||
|
||||
private ScheduledFuture<?> myScheduleFuture;
|
||||
|
||||
private IState myState = new InitialState();
|
||||
|
||||
@Autowired
|
||||
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
|
||||
|
||||
private IIdType mySubscriptionId;
|
||||
private Long mySubscriptionPid;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("websocketTaskScheduler")
|
||||
private TaskScheduler myTaskScheduler;
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
|
||||
super.afterConnectionClosed(theSession, theStatus);
|
||||
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
|
||||
super.afterConnectionEstablished(theSession);
|
||||
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
|
||||
}
|
||||
|
||||
protected void handleFailure(Exception theE) {
|
||||
ourLog.error("Failure during communication", theE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
|
||||
ourLog.info("Textmessage: " + theMessage.getPayload());
|
||||
|
||||
myState.handleTextMessage(theSession, theMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
|
||||
super.handleTransportError(theSession, theException);
|
||||
ourLog.error("Transport error", theException);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
ourLog.info("Creating scheduled task for subscription websocket connection");
|
||||
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void preDescroy() {
|
||||
ourLog.info("Cancelling scheduled task for subscription websocket connection");
|
||||
myScheduleFuture.cancel(true);
|
||||
IState state = myState;
|
||||
if (state != null) {
|
||||
state.closing();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long subscriptionPid = mySubscriptionPid;
|
||||
if (subscriptionPid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
|
||||
|
||||
List<IBaseResource> results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
|
||||
if (results.isEmpty() == false) {
|
||||
myState.deliver(results);
|
||||
}
|
||||
}
|
||||
|
||||
private class BoundDynamicSubscriptionState implements IState {
|
||||
|
||||
private EncodingEnum myEncoding;
|
||||
private WebSocketSession mySession;
|
||||
|
||||
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
|
||||
mySession = theSession;
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
ourLog.info("Deleting subscription {}", mySubscriptionId);
|
||||
try {
|
||||
mySubscriptionDao.delete(mySubscriptionId, null);
|
||||
} catch (Exception e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
try {
|
||||
for (IBaseResource nextResource : theResults) {
|
||||
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
|
||||
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
|
||||
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
|
||||
mySession.sendMessage(new TextMessage(payload));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BoundStaticSubscriptionState implements IState {
|
||||
|
||||
private EncodingEnum myEncoding;
|
||||
private WebSocketSession mySession;
|
||||
|
||||
public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
|
||||
mySession = theSession;
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
try {
|
||||
for (IBaseResource nextResource : theResults) {
|
||||
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
|
||||
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
|
||||
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
|
||||
mySession.sendMessage(new TextMessage(payload));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class InitialState implements IState {
|
||||
|
||||
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
|
||||
IdDt id = new IdDt(theBindString);
|
||||
|
||||
if (!id.hasIdPart() || !id.isIdPartValid()) {
|
||||
try {
|
||||
String message = "Invalid bind request - No ID included";
|
||||
ourLog.warn(message);
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (id.hasResourceType() == false) {
|
||||
id = id.withResourceType("Subscription");
|
||||
}
|
||||
|
||||
try {
|
||||
Subscription subscription = mySubscriptionDao.read(id, null);
|
||||
EncodingEnum encoding = EncodingEnum.JSON;
|
||||
String criteria = subscription.getCriteria();
|
||||
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())) {
|
||||
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
if (nextEncoding != null) {
|
||||
encoding = nextEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
|
||||
mySubscriptionId = subscription.getIdElement();
|
||||
myState = new BoundStaticSubscriptionState(theSession, encoding);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
try {
|
||||
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
|
||||
ourLog.warn(message);
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
|
||||
} catch (IOException e1) {
|
||||
handleFailure(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private IIdType bindSearch(WebSocketSession theSession, String theRemaining) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET);
|
||||
subscription.setStatus(SubscriptionStatusEnum.ACTIVE);
|
||||
subscription.setCriteria(theRemaining);
|
||||
|
||||
try {
|
||||
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
|
||||
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
|
||||
EncodingEnum encoding = EncodingEnum.JSON;
|
||||
for (NameValuePair nameValuePair : paramValues) {
|
||||
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
|
||||
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
if (nextEncoding != null) {
|
||||
encoding = nextEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IIdType id = mySubscriptionDao.create(subscription).getId();
|
||||
|
||||
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
|
||||
mySubscriptionId = subscription.getIdElement();
|
||||
myState = new BoundDynamicSubscriptionState(theSession, encoding);
|
||||
|
||||
return id;
|
||||
} catch (UnprocessableEntityException e) {
|
||||
ourLog.warn("Failed to bind subscription: " + e.getMessage());
|
||||
try {
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
|
||||
} catch (IOException e2) {
|
||||
handleFailure(e2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleFailure(e);
|
||||
try {
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
|
||||
} catch (IOException e2) {
|
||||
handleFailure(e2);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
String message = theMessage.getPayload();
|
||||
if (message.startsWith("bind ")) {
|
||||
String remaining = message.substring("bind ".length());
|
||||
|
||||
IIdType subscriptionId;
|
||||
if (remaining.contains("?")) {
|
||||
subscriptionId = bindSearch(theSession, remaining);
|
||||
} else {
|
||||
subscriptionId = bindSimple(theSession, remaining);
|
||||
if (subscriptionId == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface IState {
|
||||
|
||||
void closing();
|
||||
|
||||
void deliver(List<IBaseResource> theResults);
|
||||
|
||||
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,355 @@
|
||||
/*
|
||||
* 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.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoSubscription;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
|
||||
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.scheduling.TaskScheduler;
|
||||
import org.springframework.web.socket.CloseStatus;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import org.springframework.web.socket.handler.TextWebSocketHandler;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
public class SubscriptionWebsocketReturnResourceHandlerDstu3 extends TextWebSocketHandler implements ISubscriptionWebsocketHandler, Runnable {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketReturnResourceHandlerDstu3.class);
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
|
||||
private ScheduledFuture<?> myScheduleFuture;
|
||||
|
||||
private IState myState = new InitialState();
|
||||
|
||||
@Autowired
|
||||
private IFhirResourceDaoSubscription<Subscription> mySubscriptionDao;
|
||||
|
||||
private IIdType mySubscriptionId;
|
||||
private Long mySubscriptionPid;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("websocketTaskScheduler")
|
||||
private TaskScheduler myTaskScheduler;
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(WebSocketSession theSession, CloseStatus theStatus) throws Exception {
|
||||
super.afterConnectionClosed(theSession, theStatus);
|
||||
ourLog.info("Closing WebSocket connection from {}", theSession.getRemoteAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession theSession) throws Exception {
|
||||
super.afterConnectionEstablished(theSession);
|
||||
ourLog.info("Incoming WebSocket connection from {}", theSession.getRemoteAddress());
|
||||
}
|
||||
|
||||
protected void handleFailure(Exception theE) {
|
||||
ourLog.error("Failure during communication", theE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) throws Exception {
|
||||
ourLog.info("Textmessage: " + theMessage.getPayload());
|
||||
|
||||
myState.handleTextMessage(theSession, theMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession theSession, Throwable theException) throws Exception {
|
||||
super.handleTransportError(theSession, theException);
|
||||
ourLog.error("Transport error", theException);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
ourLog.info("Creating scheduled task for subscription websocket connection");
|
||||
myScheduleFuture = myTaskScheduler.scheduleWithFixedDelay(this, 1000);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void preDescroy() {
|
||||
ourLog.info("Cancelling scheduled task for subscription websocket connection");
|
||||
myScheduleFuture.cancel(true);
|
||||
IState state = myState;
|
||||
if (state != null) {
|
||||
state.closing();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long subscriptionPid = mySubscriptionPid;
|
||||
if (subscriptionPid == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ourLog.debug("Subscription {} websocket handler polling", subscriptionPid);
|
||||
|
||||
List<IBaseResource> results = mySubscriptionDao.getUndeliveredResourcesAndPurge(subscriptionPid);
|
||||
if (results.isEmpty() == false) {
|
||||
myState.deliver(results);
|
||||
}
|
||||
}
|
||||
|
||||
private class BoundDynamicSubscriptionState implements IState {
|
||||
|
||||
private EncodingEnum myEncoding;
|
||||
private WebSocketSession mySession;
|
||||
|
||||
public BoundDynamicSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
|
||||
mySession = theSession;
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
ourLog.info("Deleting subscription {}", mySubscriptionId);
|
||||
try {
|
||||
mySubscriptionDao.delete(mySubscriptionId, null);
|
||||
} catch (Exception e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
try {
|
||||
for (IBaseResource nextResource : theResults) {
|
||||
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
|
||||
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
|
||||
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
|
||||
mySession.sendMessage(new TextMessage(payload));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class BoundStaticSubscriptionState implements IState {
|
||||
|
||||
private EncodingEnum myEncoding;
|
||||
private WebSocketSession mySession;
|
||||
|
||||
public BoundStaticSubscriptionState(WebSocketSession theSession, EncodingEnum theEncoding) {
|
||||
mySession = theSession;
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
try {
|
||||
for (IBaseResource nextResource : theResults) {
|
||||
ourLog.info("Sending WebSocket message for resource: {}", nextResource.getIdElement());
|
||||
String encoded = myEncoding.newParser(myCtx).encodeResourceToString(nextResource);
|
||||
String payload = "add " + mySubscriptionId.getIdPart() + '\n' + encoded;
|
||||
mySession.sendMessage(new TextMessage(payload));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("Unexpected client message: " + theMessage.getPayload()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class InitialState implements IState {
|
||||
|
||||
private IIdType bindSimple(WebSocketSession theSession, String theBindString) {
|
||||
IdType id = new IdType(theBindString);
|
||||
|
||||
if (!id.hasIdPart() || !id.isIdPartValid()) {
|
||||
try {
|
||||
String message = "Invalid bind request - No ID included";
|
||||
ourLog.warn(message);
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (id.hasResourceType() == false) {
|
||||
id = id.withResourceType("Subscription");
|
||||
}
|
||||
|
||||
try {
|
||||
Subscription subscription = mySubscriptionDao.read(id, null);
|
||||
EncodingEnum encoding = EncodingEnum.JSON;
|
||||
String criteria = subscription.getCriteria();
|
||||
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())) {
|
||||
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
if (nextEncoding != null) {
|
||||
encoding = nextEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
|
||||
mySubscriptionId = subscription.getIdElement();
|
||||
myState = new BoundStaticSubscriptionState(theSession, encoding);
|
||||
} catch (ResourceNotFoundException e) {
|
||||
try {
|
||||
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
|
||||
ourLog.warn(message);
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), message));
|
||||
} catch (IOException e1) {
|
||||
handleFailure(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
private IIdType bindSearch(WebSocketSession theSession, String theRemaining) {
|
||||
Subscription subscription = new Subscription();
|
||||
subscription.getChannel().setType(SubscriptionChannelType.WEBSOCKET);
|
||||
subscription.setStatus(SubscriptionStatus.ACTIVE);
|
||||
subscription.setCriteria(theRemaining);
|
||||
|
||||
try {
|
||||
String params = theRemaining.substring(theRemaining.indexOf('?')+1);
|
||||
List<NameValuePair> paramValues = URLEncodedUtils.parse(params, Constants.CHARSET_UTF8, '&');
|
||||
EncodingEnum encoding = EncodingEnum.JSON;
|
||||
for (NameValuePair nameValuePair : paramValues) {
|
||||
if (Constants.PARAM_FORMAT.equals(nameValuePair.getName())) {
|
||||
EncodingEnum nextEncoding = EncodingEnum.forContentType(nameValuePair.getValue());
|
||||
if (nextEncoding != null) {
|
||||
encoding = nextEncoding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IIdType id = mySubscriptionDao.create(subscription).getId();
|
||||
|
||||
mySubscriptionPid = mySubscriptionDao.getSubscriptionTablePidForSubscriptionResource(id);
|
||||
mySubscriptionId = subscription.getIdElement();
|
||||
myState = new BoundDynamicSubscriptionState(theSession, encoding);
|
||||
|
||||
return id;
|
||||
} catch (UnprocessableEntityException e) {
|
||||
ourLog.warn("Failed to bind subscription: " + e.getMessage());
|
||||
try {
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - " + e.getMessage()));
|
||||
} catch (IOException e2) {
|
||||
handleFailure(e2);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
handleFailure(e);
|
||||
try {
|
||||
theSession.close(new CloseStatus(CloseStatus.PROTOCOL_ERROR.getCode(), "Invalid bind request - No ID included"));
|
||||
} catch (IOException e2) {
|
||||
handleFailure(e2);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closing() {
|
||||
// nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deliver(List<IBaseResource> theResults) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTextMessage(WebSocketSession theSession, TextMessage theMessage) {
|
||||
String message = theMessage.getPayload();
|
||||
if (message.startsWith("bind ")) {
|
||||
String remaining = message.substring("bind ".length());
|
||||
|
||||
IIdType subscriptionId;
|
||||
if (remaining.contains("?")) {
|
||||
subscriptionId = bindSearch(theSession, remaining);
|
||||
} else {
|
||||
subscriptionId = bindSimple(theSession, remaining);
|
||||
if (subscriptionId == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
theSession.sendMessage(new TextMessage("bound " + subscriptionId.getIdPart()));
|
||||
} catch (IOException e) {
|
||||
handleFailure(e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface IState {
|
||||
|
||||
void closing();
|
||||
|
||||
void deliver(List<IBaseResource> theResults);
|
||||
|
||||
void handleTextMessage(WebSocketSession theSession, TextMessage theMessage);
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package ca.uhn.fhir.jpa.thread;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpRequestDstu2Job implements Runnable{
|
||||
|
||||
private HttpUriRequest request;
|
||||
private Subscription subscription;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpRequestDstu2Job.class);
|
||||
|
||||
public HttpRequestDstu2Job(HttpUriRequest request, Subscription subscription){
|
||||
this.request = request;
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
executeRequest(request, subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a post back to the subscription client
|
||||
*
|
||||
* @param request
|
||||
* @param subscription
|
||||
*/
|
||||
private void executeRequest(HttpUriRequest request, Subscription subscription) {
|
||||
String url = subscription.getChannel().getEndpoint();
|
||||
|
||||
try {
|
||||
HttpClient client = HttpClientBuilder.create().build();
|
||||
client.execute(request);
|
||||
logger.info("sent: " + request.getURI());
|
||||
} catch (IOException e) {
|
||||
logger.error("Error sending rest post call from subscription " + subscription.getId() + " with endpoint " + url);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package ca.uhn.fhir.jpa.thread;
|
||||
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpUriRequest;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpRequestDstu3Job implements Runnable {
|
||||
|
||||
private HttpUriRequest request;
|
||||
private Subscription subscription;
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpRequestDstu3Job.class);
|
||||
|
||||
public HttpRequestDstu3Job(HttpUriRequest request, Subscription subscription) {
|
||||
this.request = request;
|
||||
this.subscription = subscription;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
executeRequest(request, subscription);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a post back to the subscription client
|
||||
*
|
||||
* @param request
|
||||
* @param subscription
|
||||
*/
|
||||
private void executeRequest(HttpUriRequest request, Subscription subscription) {
|
||||
String url = subscription.getChannel().getEndpoint();
|
||||
|
||||
try {
|
||||
HttpClient client = HttpClientBuilder.create().build();
|
||||
client.execute(request);
|
||||
} catch (IOException e) {
|
||||
logger.error("Error sending rest post call from subscription " + subscription.getId() + " with endpoint " + url);
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
logger.info("sent: " + url);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package ca.uhn.fhir.jpa.util;
|
||||
|
||||
/**
|
||||
* Created by Jeff on 2/8/2017.
|
||||
*/
|
||||
public enum MethodRequest {
|
||||
POST,
|
||||
GET,
|
||||
PUT,
|
||||
DELETE
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
|
||||
import javax.xml.ws.http.HTTPException;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Rest service utilities. Generally used in the tests
|
||||
*/
|
||||
public class RestUtilities {
|
||||
|
||||
public static final String CONTEXT_PATH = "";
|
||||
public static final String APPLICATION_JSON = "application/json";
|
||||
|
||||
/**
|
||||
* Get the response for a CXF REST service without an object parameter
|
||||
*
|
||||
* @param url
|
||||
* @param typeRequest
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String getResponse(String url, MethodRequest typeRequest) throws IOException {
|
||||
return getResponse(url, (StringEntity) null, typeRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the response for a CXF REST service with an object parameter
|
||||
*
|
||||
* @param url
|
||||
* @param parameterEntity
|
||||
* @param typeRequest
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String getResponse(String url, StringEntity parameterEntity, MethodRequest typeRequest) throws IOException {
|
||||
HttpClient httpclient = new DefaultHttpClient();
|
||||
HttpResponse response;
|
||||
|
||||
switch (typeRequest) {
|
||||
case POST:
|
||||
HttpPost httppost = new HttpPost(url);
|
||||
httppost.setHeader("Content-type", APPLICATION_JSON);
|
||||
if (parameterEntity != null) {
|
||||
httppost.setEntity(parameterEntity);
|
||||
}
|
||||
response = httpclient.execute(httppost);
|
||||
break;
|
||||
case PUT:
|
||||
HttpPut httpPut = new HttpPut(url);
|
||||
httpPut.setHeader("Content-type", APPLICATION_JSON);
|
||||
if (parameterEntity != null) {
|
||||
httpPut.setEntity(parameterEntity);
|
||||
}
|
||||
response = httpclient.execute(httpPut);
|
||||
break;
|
||||
case DELETE:
|
||||
HttpDelete httpDelete = new HttpDelete(url);
|
||||
httpDelete.setHeader("Content-type", APPLICATION_JSON);
|
||||
response = httpclient.execute(httpDelete);
|
||||
break;
|
||||
case GET:
|
||||
HttpGet httpGet = new HttpGet(url);
|
||||
httpGet.setHeader("Content-type", APPLICATION_JSON);
|
||||
response = httpclient.execute(httpGet);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot handle type request " + typeRequest);
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) {
|
||||
throw new HTTPException(response.getStatusLine().getStatusCode());
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 204) {
|
||||
return "";
|
||||
}
|
||||
|
||||
//Closes connections that have already been closed by the server
|
||||
//org.apache.http.NoHttpResponseException: The target server failed to respond
|
||||
httpclient.getConnectionManager().closeIdleConnections(1, TimeUnit.SECONDS);
|
||||
|
||||
return EntityUtils.toString(response.getEntity());
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import org.springframework.aop.framework.Advised;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
|
||||
/**
|
||||
* Utility to get the Spring proxy object's target object
|
||||
*/
|
||||
public class SpringObjectCaster {
|
||||
|
||||
/**
|
||||
* Retrieve the Spring proxy object's target object
|
||||
* @param proxy
|
||||
* @param clazz
|
||||
* @param <T>
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static <T> T getTargetObject(Object proxy, Class<T> clazz) throws Exception {
|
||||
while( (AopUtils.isJdkDynamicProxy(proxy))) {
|
||||
return clazz.cast(getTargetObject(((Advised)proxy).getTargetSource().getTarget(), clazz));
|
||||
}
|
||||
|
||||
return clazz.cast(proxy);
|
||||
}
|
||||
}
|
@ -30,6 +30,28 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-api</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-client</artifactId>
|
||||
<version>${jetty_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>6.0.5</version>
|
||||
</dependency>
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.8.9</version>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<!-- This dependency includes the core HAPI-FHIR classes -->
|
||||
<dependency>
|
||||
@ -93,6 +115,18 @@
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Used for CORS support -->
|
||||
<dependency>
|
||||
<groupId>org.ebaysf.web</groupId>
|
||||
<artifactId>cors-filter</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<groupId>javax.servlet</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Web is used to deploy the server to a web container. -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
|
@ -0,0 +1,233 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,190 @@
|
||||
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));
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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));
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>
|
||||
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfigWSocket
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<!-- Servlets -->
|
||||
|
||||
<servlet>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<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.JpaServerDemoDstu2</servlet-class>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>FhirVersion</param-name>
|
||||
<param-value>DSTU2</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<url-pattern>/baseDstu2/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
|
||||
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
|
||||
<filter>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
|
||||
<param-name>cors.allowed.origins</param-name>
|
||||
<param-value>*</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
|
||||
<param-name>cors.allowed.methods</param-name>
|
||||
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
|
||||
<param-name>cors.allowed.headers</param-name>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
<param-name>cors.support.credentials</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag to control logging</description>
|
||||
<param-name>cors.logging.enabled</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
|
||||
<param-name>cors.preflight.maxage</param-name>
|
||||
<param-value>300</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
|
||||
</web-app>
|
@ -0,0 +1,108 @@
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>
|
||||
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3WSocket
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<!-- Servlets -->
|
||||
|
||||
<servlet>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>ca.uhn.fhir.jpa.demo.FhirTesterConfigDstu3</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>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>FhirVersion</param-name>
|
||||
<param-value>DSTU3</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<url-pattern>/baseDstu3/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
|
||||
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
|
||||
<filter>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
|
||||
<param-name>cors.allowed.origins</param-name>
|
||||
<param-value>*</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
|
||||
<param-name>cors.allowed.methods</param-name>
|
||||
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
|
||||
<param-name>cors.allowed.headers</param-name>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
<param-name>cors.support.credentials</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag to control logging</description>
|
||||
<param-name>cors.logging.enabled</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
|
||||
<param-name>cors.preflight.maxage</param-name>
|
||||
<param-value>300</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
|
||||
</web-app>
|
@ -0,0 +1,108 @@
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>
|
||||
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfig
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
<!-- Servlets -->
|
||||
|
||||
<servlet>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<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.JpaServerDemo</servlet-class>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>FhirVersion</param-name>
|
||||
<param-value>DSTU2</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<url-pattern>/baseDstu2/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>spring</servlet-name>
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
|
||||
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
|
||||
<filter>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
|
||||
<param-name>cors.allowed.origins</param-name>
|
||||
<param-value>*</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
|
||||
<param-name>cors.allowed.methods</param-name>
|
||||
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
|
||||
<param-name>cors.allowed.headers</param-name>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
<param-name>cors.support.credentials</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag to control logging</description>
|
||||
<param-name>cors.logging.enabled</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
|
||||
<param-name>cors.preflight.maxage</param-name>
|
||||
<param-value>300</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
|
||||
</web-app>
|
@ -1,5 +1,5 @@
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee ./xsd/web-app_3_0.xsd">
|
||||
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
@ -13,7 +13,7 @@
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3
|
||||
ca.uhn.fhir.jpa.demo.FhirServerConfigDstu3WSocket
|
||||
</param-value>
|
||||
</context-param>
|
||||
|
||||
@ -35,7 +35,7 @@
|
||||
|
||||
<servlet>
|
||||
<servlet-name>fhirServlet</servlet-name>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemo</servlet-class>
|
||||
<servlet-class>ca.uhn.fhir.jpa.demo.JpaServerDemoDstu3</servlet-class>
|
||||
<init-param>
|
||||
<param-name>ImplementationDescription</param-name>
|
||||
<param-value>FHIR JPA Server</param-value>
|
||||
@ -57,4 +57,52 @@
|
||||
<url-pattern>/</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
|
||||
|
||||
<!-- This filters provide support for Cross Origin Resource Sharing (CORS) -->
|
||||
<filter>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<filter-class>org.ebaysf.web.cors.CORSFilter</filter-class>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed origins. Note: An '*' cannot be used for an allowed origin when using credentials.</description>
|
||||
<param-name>cors.allowed.origins</param-name>
|
||||
<param-value>*</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of HTTP verbs, using which a CORS request can be made.</description>
|
||||
<param-name>cors.allowed.methods</param-name>
|
||||
<param-value>GET,POST,PUT,DELETE,OPTIONS</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list of allowed headers when making a non simple CORS request.</description>
|
||||
<param-name>cors.allowed.headers</param-name>
|
||||
<param-value>X-FHIR-Starter,Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A comma separated list non-standard response headers that will be exposed to XHR2 object.</description>
|
||||
<param-name>cors.exposed.headers</param-name>
|
||||
<param-value>Location,Content-Location</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag that suggests if CORS is supported with cookies</description>
|
||||
<param-name>cors.support.credentials</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>A flag to control logging</description>
|
||||
<param-name>cors.logging.enabled</param-name>
|
||||
<param-value>true</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<description>Indicates how long (in seconds) the results of a preflight request can be cached in a preflight result cache.</description>
|
||||
<param-name>cors.preflight.maxage</param-name>
|
||||
<param-value>300</param-value>
|
||||
</init-param>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>CORS Filter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
|
||||
</web-app>
|
||||
|
@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
|
||||
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.valueset.AdministrativeGenderEnum;
|
||||
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 org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public class FhirDstu2Util {
|
||||
|
||||
public static final String LPI_CODESYSTEM = "http://cognitivemedicine.com/lpi";
|
||||
public static final String LPI_CODE = "LPI-FHIR";
|
||||
|
||||
public static 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(SubscriptionStatusEnum.REQUESTED);
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Subscription.Channel channel = new Subscription.Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
channel.setPayload(payload);
|
||||
channel.setEndpoint(endpoint);
|
||||
subscription.setChannel(channel);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(subscription).execute();
|
||||
subscription.setId(methodOutcome.getId().getIdPart());
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
||||
public static Observation getSnomedObservation() {
|
||||
CodingDt snomedCoding = new CodingDt();
|
||||
snomedCoding.setSystem("SNOMED-CT");
|
||||
snomedCoding.setCode("1000000050");
|
||||
|
||||
Observation observation = new Observation();
|
||||
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
observation.getCode().addCoding(snomedCoding);
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
public static Observation getLoincObservation() {
|
||||
CodingDt snomedCoding = new CodingDt();
|
||||
snomedCoding.setSystem("http://loinc.org");
|
||||
snomedCoding.setCode("55284-4");
|
||||
snomedCoding.setDisplay("Blood Pressure");
|
||||
|
||||
Observation observation = new Observation();
|
||||
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
observation.getCode().addCoding(snomedCoding);
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
public static Patient getPatient() {
|
||||
String patientId = "1";
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setGender(AdministrativeGenderEnum.MALE);
|
||||
|
||||
IdentifierDt identifier = patient.addIdentifier();
|
||||
identifier.setValue(patientId);
|
||||
identifier.setSystem(LPI_CODESYSTEM);
|
||||
|
||||
IBaseMetaType meta = patient.getMeta();
|
||||
IBaseCoding tag = meta.addTag();
|
||||
tag.setCode(LPI_CODE);
|
||||
tag.setSystem(LPI_CODESYSTEM);
|
||||
|
||||
setTag(patient);
|
||||
|
||||
return patient;
|
||||
}
|
||||
|
||||
public static void setTag(IBaseResource resource) {
|
||||
IBaseMetaType meta = resource.getMeta();
|
||||
IBaseCoding tag = meta.addTag();
|
||||
tag.setCode(LPI_CODE);
|
||||
tag.setSystem(LPI_CODESYSTEM);
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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 org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public class FhirDstu3Util {
|
||||
|
||||
public static final String LPI_CODESYSTEM = "http://cognitivemedicine.com/lpi";
|
||||
public static final String LPI_CODE = "LPI-FHIR";
|
||||
|
||||
public static 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 static Observation getSnomedObservation() {
|
||||
Coding snomedCoding = new Coding();
|
||||
snomedCoding.setSystem("SNOMED-CT");
|
||||
snomedCoding.setCode("1000000050");
|
||||
|
||||
Observation observation = new Observation();
|
||||
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
observation.getCode().addCoding(snomedCoding);
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
public static Observation getLoincObservation() {
|
||||
Coding snomedCoding = new Coding();
|
||||
snomedCoding.setSystem("http://loinc.org");
|
||||
snomedCoding.setCode("55284-4");
|
||||
snomedCoding.setDisplay("Blood Pressure");
|
||||
|
||||
Observation observation = new Observation();
|
||||
|
||||
observation.setStatus(Observation.ObservationStatus.FINAL);
|
||||
observation.getCode().addCoding(snomedCoding);
|
||||
|
||||
return observation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a patient object for the test
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Patient getPatient() {
|
||||
String patientId = "1";
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||
|
||||
Identifier identifier = patient.addIdentifier();
|
||||
identifier.setValue(patientId);
|
||||
identifier.setSystem(LPI_CODESYSTEM);
|
||||
|
||||
IBaseMetaType meta = patient.getMeta();
|
||||
IBaseCoding tag = meta.addTag();
|
||||
tag.setCode(LPI_CODE);
|
||||
tag.setSystem(LPI_CODESYSTEM);
|
||||
setTag(patient);
|
||||
return patient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tag for a resource
|
||||
*
|
||||
* @param resource
|
||||
*/
|
||||
public static void setTag(IBaseResource resource) {
|
||||
IBaseMetaType meta = resource.getMeta();
|
||||
IBaseCoding tag = meta.addTag();
|
||||
tag.setCode(LPI_CODE);
|
||||
tag.setSystem(LPI_CODESYSTEM);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.context.FhirContext;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public class FhirServiceUtil {
|
||||
|
||||
public static final String FHIR_DSTU3_URL = "http://localhost:9093/baseDstu3";
|
||||
public static final String FHIR_DSTU2_URL = "http://localhost:9092/baseDstu2";
|
||||
public static final String JSON_PAYLOAD = "application/json";
|
||||
public static final String XML_PAYLOAD = "application/xml";
|
||||
public static final String REST_HOOK_ENDPOINT = "http://localhost:10080/rest-hook";
|
||||
|
||||
public static IGenericClient getFhirDstu3Client() {
|
||||
FhirContext ctx = FhirContext.forDstu3();
|
||||
return ctx.newRestfulGenericClient(FHIR_DSTU3_URL);
|
||||
}
|
||||
|
||||
public static IGenericClient getFhirDstu2Client() {
|
||||
FhirContext ctx = FhirContext.forDstu2();
|
||||
return ctx.newRestfulGenericClient(FHIR_DSTU2_URL);
|
||||
}
|
||||
|
||||
public static String createResource(IBaseResource resource, IGenericClient client) {
|
||||
MethodOutcome response = client.create().resource(resource).execute();
|
||||
resource.setId(response.getId());
|
||||
|
||||
return response.getId().getIdPart();
|
||||
}
|
||||
|
||||
public static String updateResource(IBaseResource resource, IGenericClient client) {
|
||||
MethodOutcome response = client.update().resource(resource).execute();
|
||||
return response.getId().getIdPart();
|
||||
}
|
||||
|
||||
public static void deleteResource(String id, Class clazz, IGenericClient client) {
|
||||
client.delete().resourceById(clazz.getSimpleName(), id).execute();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.gclient.IQuery;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Ignore
|
||||
public class RemoveDstu2TestIT {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RemoveDstu2TestIT.class);
|
||||
public static final int NUM_TO_DELETE_PER_QUERY = 100;
|
||||
|
||||
@Test
|
||||
public void remove() {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu2Client();
|
||||
deleteResources(Subscription.class, null, client);
|
||||
deleteResources(Observation.class, null, client);
|
||||
Bundle bundle = searchResources(Observation.class, null, NUM_TO_DELETE_PER_QUERY, client);
|
||||
Assert.assertNotNull(bundle);
|
||||
List<Bundle.Entry> entry = bundle.getEntry();
|
||||
Assert.assertTrue(entry.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete resources from specified class and tag
|
||||
*
|
||||
* @param clazz
|
||||
* @param tag
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T extends IBaseResource> void deleteResources(Class<T> clazz, IBaseCoding tag, IGenericClient client) {
|
||||
Bundle bundle = searchResources(clazz, tag, NUM_TO_DELETE_PER_QUERY, client);
|
||||
List<Bundle.Entry> bundleEntryComponents = bundle.getEntry();
|
||||
|
||||
while (bundleEntryComponents.size() > 0) {
|
||||
for (Bundle.Entry bundleEntryComponent : bundleEntryComponents) {
|
||||
IBaseResource resource = bundleEntryComponent.getResource();
|
||||
String id = resource.getIdElement().getIdPart();
|
||||
String className = clazz.getSimpleName();
|
||||
|
||||
logger.info("deleting resource------------------------------------------>" + className + "/" + id);
|
||||
|
||||
client.delete().resourceById(className, id).execute();
|
||||
}
|
||||
bundle = searchResources(clazz, tag, NUM_TO_DELETE_PER_QUERY, client);
|
||||
bundleEntryComponents = bundle.getEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resources from specified class and tag
|
||||
*
|
||||
* @param clazz
|
||||
* @param tag
|
||||
* @param limit
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T extends IBaseResource> Bundle searchResources(Class<T> clazz, IBaseCoding tag, Integer limit, IGenericClient client) {
|
||||
IQuery iquery = client.search().forResource(clazz);
|
||||
|
||||
if (tag != null) {
|
||||
iquery.withTag(tag.getSystem(), tag.getCode());
|
||||
}
|
||||
|
||||
if (limit != null) {
|
||||
iquery.count(limit);
|
||||
}
|
||||
|
||||
return (Bundle) iquery.returnBundle(Bundle.class).execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.client.IGenericClient;
|
||||
import ca.uhn.fhir.rest.gclient.IQuery;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Ignore
|
||||
public class RemoveDstu3TestIT {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RemoveDstu3TestIT.class);
|
||||
public static final int NUM_TO_DELETE_PER_QUERY = 10000;
|
||||
|
||||
@Test
|
||||
public void remove() {
|
||||
IGenericClient client = FhirServiceUtil.getFhirDstu3Client();
|
||||
deleteResources(Subscription.class, null, client);
|
||||
deleteResources(Observation.class, null, client);
|
||||
/* try {
|
||||
//wait for cache to clear
|
||||
Thread.sleep(3000);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
*/
|
||||
Bundle bundle = searchResources(Observation.class, null, NUM_TO_DELETE_PER_QUERY, client);
|
||||
Assert.assertNotNull(bundle);
|
||||
List<Bundle.BundleEntryComponent> entry = bundle.getEntry();
|
||||
Assert.assertTrue(entry.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete resources from specified class and tag
|
||||
*
|
||||
* @param clazz
|
||||
* @param tag
|
||||
* @param <T>
|
||||
*/
|
||||
public static <T extends IBaseResource> void deleteResources(Class<T> clazz, IBaseCoding tag, IGenericClient client) {
|
||||
Bundle bundle = searchResources(clazz, tag, NUM_TO_DELETE_PER_QUERY, client);
|
||||
List<Bundle.BundleEntryComponent> bundleEntryComponents = bundle.getEntry();
|
||||
|
||||
// while (bundleEntryComponents.size() > 0) {
|
||||
for (Bundle.BundleEntryComponent bundleEntryComponent : bundleEntryComponents) {
|
||||
IBaseResource resource = bundleEntryComponent.getResource();
|
||||
String id = resource.getIdElement().getIdPart();
|
||||
String className = clazz.getSimpleName();
|
||||
|
||||
logger.info("deleting resource------------------------------------------>" + className + "/" + id);
|
||||
|
||||
client.delete().resourceById(className, id).execute();
|
||||
}
|
||||
// currently loops forever due to the FHIR server using a cached query result
|
||||
// bundle = searchResources(clazz, tag, NUM_TO_DELETE_PER_QUERY, client);
|
||||
// bundleEntryComponents = bundle.getEntry();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resources from specified class and tag
|
||||
*
|
||||
* @param clazz
|
||||
* @param tag
|
||||
* @param limit
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T extends IBaseResource> Bundle searchResources(Class<T> clazz, IBaseCoding tag, Integer limit, IGenericClient client) {
|
||||
IQuery iquery = client.search().forResource(clazz);
|
||||
|
||||
if (tag != null) {
|
||||
iquery.withTag(tag.getSystem(), tag.getCode());
|
||||
}
|
||||
|
||||
if (limit != null) {
|
||||
iquery.count(limit);
|
||||
}
|
||||
|
||||
return (Bundle) iquery.returnBundle(Bundle.class).execute();
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 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 RestHookTestDstu2IT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
private static String code = "1000000012";
|
||||
private IGenericClient client = FhirServiceUtil.getFhirDstu2Client();
|
||||
|
||||
@Before
|
||||
public void clean() {
|
||||
RemoveDstu2TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu2TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscription() {
|
||||
String payload = "application/json";
|
||||
String endpoint = "http://localhost:10080/rest-hook";
|
||||
|
||||
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 observationTemp1 = sendObservation(code, "SNOMED-CT", client);
|
||||
Observation observation1 = client.read(Observation.class, observationTemp1.getId());
|
||||
//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 observationTemp2 = sendObservation(code, "SNOMED-CT", client);
|
||||
Observation observation2 = client.read(Observation.class, observationTemp2.getId());
|
||||
//Should see two subscription notifications
|
||||
|
||||
client.delete().resourceById("Subscription", subscription2.getId().getIdPart()).execute();
|
||||
|
||||
Observation observationTemp3 = sendObservation(code, "SNOMED-CT", client);
|
||||
//Should see only one subscription notification
|
||||
|
||||
Observation observation3 = client.read(Observation.class, observationTemp1.getId());
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation3.setCode(codeableConcept);
|
||||
CodingDt 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, observationTemp1.getId());
|
||||
CodeableConceptDt codeableConcept2 = new CodeableConceptDt();
|
||||
observation3a.setCode(codeableConcept2);
|
||||
CodingDt coding2 = codeableConcept2.addCoding();
|
||||
coding2.setCode(code);
|
||||
coding2.setSystem("SNOMED-CT");
|
||||
client.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
|
||||
//Should see only one subscription notification
|
||||
|
||||
System.out.println("subscription id 1: " + subscription1.getId());
|
||||
System.out.println("subscription id 2: " + subscription2.getId());
|
||||
System.out.println("subscription temp id 2: " + subscriptionTemp.getId());
|
||||
System.out.println("observation id 1: " + observation1.getId());
|
||||
System.out.println("observation id 2: " + observation2.getId());
|
||||
System.out.println("observation id 3: " + observation3.getId());
|
||||
|
||||
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(SubscriptionStatusEnum.REQUESTED);
|
||||
subscription.setCriteria(criteria);
|
||||
|
||||
Subscription.Channel channel = new Subscription.Channel();
|
||||
channel.setType(SubscriptionChannelTypeEnum.REST_HOOK);
|
||||
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();
|
||||
CodeableConceptDt codeableConcept = new CodeableConceptDt();
|
||||
observation.setCode(codeableConcept);
|
||||
CodingDt coding = codeableConcept.addCoding();
|
||||
coding.setCode(code);
|
||||
coding.setSystem(system);
|
||||
|
||||
observation.setStatus(ObservationStatusEnum.FINAL);
|
||||
|
||||
MethodOutcome methodOutcome = client.create().resource(observation).execute();
|
||||
|
||||
String observationId = methodOutcome.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
|
||||
return observation;
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
@Ignore
|
||||
public class RestHookTestDstu3WithSubscriptionResponseCriteriaIT {
|
||||
|
||||
private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirSubscriptionWithSubscriptionIdDstu3IT.class);
|
||||
|
||||
@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, "Observation?_format=xml", endpoint, client);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload, endpoint, client);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT", client);
|
||||
//Should see a bundle
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.resource.Observation;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Subscription;
|
||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Must have a fhir server and web service endpoint to run these tests which subscribe to the fhir and receive notifications
|
||||
*/
|
||||
@Ignore
|
||||
public class ResthookSubscriptionDstu2TestsIT {
|
||||
|
||||
private static IGenericClient client;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
client = FhirServiceUtil.getFhirDstu2Client();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clean() {
|
||||
RemoveDstu2TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu2TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithoutPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation loincObservation = FhirDstu2Util.getLoincObservation();
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(loincObservation, client); //should not trigger a notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
|
||||
snomedObservation.setComments("mock change");
|
||||
|
||||
FhirServiceUtil.updateResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.deleteResource(snomedObservation.getIdElement().getIdPart(), Observation.class, client); //should trigger one notification
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithXmlPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, FhirServiceUtil.XML_PAYLOAD, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml resource in the body
|
||||
|
||||
snomedObservation.setComments("mock change");
|
||||
|
||||
FhirServiceUtil.updateResource(snomedObservation, client); //should trigger one notification with xml resource in the body
|
||||
FhirServiceUtil.deleteResource(snomedObservation.getIdElement().getIdPart(), Observation.class, client); //should trigger one notification with xml resource in the body
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithJsonPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, FhirServiceUtil.JSON_PAYLOAD, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with json resource in the body
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomXmlPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&_format=xml";
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml bundle resource in the body containing three observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomJsonPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&_format=json";
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing two observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomDefaultPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria;
|
||||
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing one observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomDefaultPayloadThatIsEmpty() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/Observation?code=SNOMED-CT|" + code + "1111";
|
||||
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing no observations
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 5 second delay to the HttpRequestDstu3Job to test if threading is improving creation speed
|
||||
*/
|
||||
@Test
|
||||
public void testSubscriptionsThreading() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
System.out.println("start");
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 5 second delay to the HttpRequestDstu3Job to test if threading is improving creation speed
|
||||
*/
|
||||
@Test
|
||||
public void testSubscriptionsThreading2() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
Observation snomedObservation = FhirDstu2Util.getSnomedObservation();
|
||||
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu2Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
|
||||
System.out.println("start");
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
System.out.println("done");
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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.client.IGenericClient;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.hl7.fhir.dstu3.model.Observation;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.*;
|
||||
|
||||
/**
|
||||
* Must have a fhir server and web service endpoint to run these
|
||||
* tests which subscribe to the fhir and receive notifications
|
||||
*/
|
||||
@Ignore
|
||||
public class ResthookSubscriptionDstu3TestsIT {
|
||||
|
||||
private static IGenericClient client;
|
||||
|
||||
@BeforeClass
|
||||
public static void init() {
|
||||
client = FhirServiceUtil.getFhirDstu3Client();
|
||||
}
|
||||
|
||||
//@Before
|
||||
//@Test
|
||||
public void clean() {
|
||||
RemoveDstu3TestIT.deleteResources(Subscription.class, null, client);
|
||||
RemoveDstu3TestIT.deleteResources(Observation.class, null, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createSnomedObservation() {
|
||||
String id = FhirServiceUtil.createResource(FhirDstu3Util.getSnomedObservation(), client);
|
||||
FhirServiceUtil.deleteResource(id, Observation.class, client);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithoutPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation loincObservation = FhirDstu3Util.getLoincObservation();
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
// FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(loincObservation, client); //should not trigger a notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
|
||||
snomedObservation.setComment("mock change");
|
||||
|
||||
FhirServiceUtil.updateResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.deleteResource(snomedObservation.getIdElement().getIdPart(), Observation.class, client); //should trigger one notification
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithXmlPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, FhirServiceUtil.XML_PAYLOAD, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml resource in the body
|
||||
|
||||
snomedObservation.setComment("mock change");
|
||||
|
||||
FhirServiceUtil.updateResource(snomedObservation, client); //should trigger one notification with xml resource in the body
|
||||
FhirServiceUtil.deleteResource(snomedObservation.getIdElement().getIdPart(), Observation.class, client); //should trigger one notification with xml resource in the body
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithJsonPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, FhirServiceUtil.JSON_PAYLOAD, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with json resource in the body
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomXmlPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&_format=xml";
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml bundle resource in the body containing three observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomJsonPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&_format=json";
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing two observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomDefaultPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria;
|
||||
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing one observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithCustomDefaultPayloadThatIsEmpty() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/Observation?code=SNOMED-CT|" + code + "1111";
|
||||
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with JSON bundle resource in the body containing no observations
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 5 second delay to the HttpRequestDstu3Job to test if threading is improving creation speed
|
||||
*/
|
||||
@Test
|
||||
public void testSubscriptionsThreading() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
System.out.println("start");
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 5 second delay to the HttpRequestDstu3Job to test if threading is improving creation speed
|
||||
*/
|
||||
@Test
|
||||
public void testSubscriptionsThreading2() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
FhirDstu3Util.createSubscription(criteria, null, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
|
||||
System.out.println("start");
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWithTMinusPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&date=Tminus100s" + "&_format=xml";
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
DateTimeType dateTimeType = DateTimeType.now();
|
||||
dateTimeType.setYear(2017);
|
||||
dateTimeType.setMonth(2);
|
||||
dateTimeType.setDay(1);
|
||||
System.out.println(dateTimeType.getValueAsString());
|
||||
snomedObservation.setEffective(dateTimeType);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
snomedObservation.setEffective(DateTimeType.now());
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
snomedObservation.setEffective(DateTimeType.now());
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml bundle resource in the body containing two observations
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscriptionsWith2TMinusPayload() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = "application/fhir+query/" + criteria + "&date=Tminus2m" + "&_lastUpdated=Tminus20s" + "&_format=xml";
|
||||
Observation snomedObservation = FhirDstu3Util.getSnomedObservation();
|
||||
DateTimeType dateTimeType = DateTimeType.now();
|
||||
dateTimeType.setYear(2017);
|
||||
dateTimeType.setMonth(2);
|
||||
dateTimeType.setDay(1);
|
||||
System.out.println(dateTimeType.getValueAsString());
|
||||
snomedObservation.setEffective(dateTimeType);
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
snomedObservation.setEffective(DateTimeType.now());
|
||||
FhirServiceUtil.createResource(snomedObservation, client);
|
||||
FhirDstu3Util.createSubscription(criteria, payloadCriteria, FhirServiceUtil.REST_HOOK_ENDPOINT, client);
|
||||
snomedObservation.setEffective(DateTimeType.now());
|
||||
FhirServiceUtil.createResource(snomedObservation, client); //should trigger one notification with xml bundle resource in the body containing two observations
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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.jpa.service.TMinusService;
|
||||
import org.hl7.fhir.dstu3.model.DateTimeType;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TminusTest {
|
||||
|
||||
@Test
|
||||
public void testDays() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&effectiveDate=Tminus10d" + "&noEffectiveDate=Tminus1d" + "&_format=xml";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("Tminus10d with the current datetime - 10d");
|
||||
payloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(!payloadCriteria.contains("Tminus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeconds() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&date=Tminus200s" + "&_format=xml";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("replace Tminus200s with the current datetime - 200s");
|
||||
payloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(!payloadCriteria.contains("Tminus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinutes() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&effectiveDate=Tminus1m" + "&_format=xml";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("replace Tminus1m with the current datetime - 1m");
|
||||
payloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(!payloadCriteria.contains("Tminus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMinutesAtTheEnd() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&date=Tminus1m";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("replace Tminus1m with the current datetime - 1m");
|
||||
payloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(!payloadCriteria.contains("Tminus"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutTminus() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria;
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("test without a Tminus");
|
||||
String newPayloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(!payloadCriteria.contains("Tminus"));
|
||||
Assert.assertTrue(payloadCriteria.equals(newPayloadCriteria));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutTminusUnits() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&date=Tminus1";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("check Tminus without units");
|
||||
String newPayloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(payloadCriteria.contains("Tminus"));
|
||||
Assert.assertTrue(payloadCriteria.equals(newPayloadCriteria));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutTminusValue() {
|
||||
String code = "1000000050";
|
||||
String criteria = "Observation?code=SNOMED-CT|" + code;
|
||||
String payloadCriteria = criteria + "&date=Tminusm";
|
||||
System.out.println(payloadCriteria);
|
||||
System.out.println("check Tminus without a value");
|
||||
String newPayloadCriteria = TMinusService.parseCriteria(payloadCriteria);
|
||||
System.out.println(payloadCriteria);
|
||||
Assert.assertTrue(payloadCriteria.contains("Tminus"));
|
||||
Assert.assertTrue(payloadCriteria.equals(newPayloadCriteria));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parsingCriteria() {
|
||||
String criteria = "Observation?code=SNOMED-CT|1000000050&date=Tminus1d&_format=xml";
|
||||
//String criteria = "Observation?code=SNOMED-CT|1000000050&date=%3E%3D2017-03-05T12:55:02-08:00&_format=xml";
|
||||
|
||||
String TMINUS = "Tminus";
|
||||
String WEEK = "w";
|
||||
String DAY = "d";
|
||||
String HOUR = "h";
|
||||
String MINUTE = "m";
|
||||
String SECOND = "s";
|
||||
|
||||
long MINUTE_AS_MILLIS = 60 * 1000;
|
||||
long HOUR_AS_MILLIS = 60 * 60 * 1000;
|
||||
long DAY_AS_MILLIS = 24 * 60 * 60 * 1000;
|
||||
long WEEK_AS_MILLIS = 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
Pattern pattern = Pattern.compile("Tminus([0-9]+)([wdhms])");
|
||||
Matcher matcher = pattern.matcher(criteria);
|
||||
|
||||
if (matcher.find()) {
|
||||
String tMinus = matcher.group();
|
||||
String tMinusValue = tMinus.substring(TMINUS.length(), tMinus.length() - 1);
|
||||
String tMinusPeriod = tMinus.substring(tMinus.length() - 1);
|
||||
|
||||
long tMinusLongValue = Long.parseLong(tMinusValue);
|
||||
long tMinusMillis = 0L;
|
||||
|
||||
if (WEEK.equals(tMinusPeriod)) {
|
||||
tMinusMillis = WEEK_AS_MILLIS * tMinusLongValue;
|
||||
} else if (DAY.equals(tMinusPeriod)) {
|
||||
tMinusMillis = DAY_AS_MILLIS * tMinusLongValue;
|
||||
} else if (HOUR.equals(tMinusPeriod)) {
|
||||
tMinusMillis = HOUR_AS_MILLIS * tMinusLongValue;
|
||||
} else if (MINUTE.equals(tMinusPeriod)) {
|
||||
tMinusMillis = MINUTE_AS_MILLIS * tMinusLongValue;
|
||||
} else if (SECOND.equals(tMinusPeriod)) {
|
||||
tMinusMillis = 1000 * tMinusLongValue;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Period not recognized: " + tMinusPeriod);
|
||||
}
|
||||
|
||||
Date currentDate = new Date();
|
||||
Date lowerDate = new Date(currentDate.getTime() - tMinusMillis);
|
||||
|
||||
DateTimeType lowerDateTimeType = new DateTimeType(lowerDate);
|
||||
DateTimeType currentDateTimeType = new DateTimeType(currentDate);
|
||||
|
||||
String dateValue = "%3E%3D" + lowerDateTimeType.getValueAsString() + "&date=%3C%3D" + currentDateTimeType.getValueAsString();
|
||||
String formattedCriteria = criteria.replace(tMinus, dateValue);
|
||||
|
||||
System.out.println(tMinus);
|
||||
System.out.println(tMinusValue);
|
||||
System.out.println(tMinusPeriod);
|
||||
System.out.println(tMinusMillis);
|
||||
System.out.println(currentDate);
|
||||
System.out.println(lowerDate);
|
||||
System.out.println(lowerDateTimeType.getValueAsString());
|
||||
System.out.println(formattedCriteria);
|
||||
} else {
|
||||
System.out.println("nothing");
|
||||
}
|
||||
}
|
||||
}
|
6
pom.xml
6
pom.xml
@ -370,6 +370,7 @@
|
||||
<slf4j_target_version>1.6.0</slf4j_target_version>
|
||||
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<ebay_cors_filter_version>1.0.1</ebay_cors_filter_version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@ -644,6 +645,11 @@
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
<version>4.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ebaysf.web</groupId>
|
||||
<artifactId>cors-filter</artifactId>
|
||||
<version>${ebay_cors_filter_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http</artifactId>
|
||||
|
Loading…
x
Reference in New Issue
Block a user