parent
f55be0b6d0
commit
10c59fceeb
|
@ -1,6 +1,9 @@
|
||||||
package ca.uhn.fhir.util;
|
package ca.uhn.fhir.util;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import com.google.common.escape.Escaper;
|
import com.google.common.escape.Escaper;
|
||||||
|
@ -172,8 +175,13 @@ public class UrlUtil {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static RuntimeResourceDefinition parseUrlResourceType(FhirContext theCtx, String theUrl) throws DataFormatException {
|
||||||
System.out.println(escapeUrlParam("http://snomed.info/sct?fhir_vs=isa/126851005"));
|
int paramIndex = theUrl.indexOf('?');
|
||||||
|
String resourceName = theUrl.substring(0, paramIndex);
|
||||||
|
if (resourceName.contains("/")) {
|
||||||
|
resourceName = resourceName.substring(resourceName.lastIndexOf('/') + 1);
|
||||||
|
}
|
||||||
|
return theCtx.getResourceDefinition(resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, String[]> parseQueryString(String theQueryString) {
|
public static Map<String, String[]> parseQueryString(String theQueryString) {
|
||||||
|
|
|
@ -26,9 +26,9 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
@ -77,7 +77,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
private void refreshNow(WarmCacheEntry theCacheEntry) {
|
private void refreshNow(WarmCacheEntry theCacheEntry) {
|
||||||
String nextUrl = theCacheEntry.getUrl();
|
String nextUrl = theCacheEntry.getUrl();
|
||||||
|
|
||||||
RuntimeResourceDefinition resourceDef = parseUrlResourceType(myCtx, nextUrl);
|
RuntimeResourceDefinition resourceDef = UrlUtil.parseUrlResourceType(myCtx, nextUrl);
|
||||||
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
|
IFhirResourceDao<?> callingDao = myDaoRegistry.getResourceDao(resourceDef.getName());
|
||||||
String queryPart = parseWarmUrlParamPart(nextUrl);
|
String queryPart = parseWarmUrlParamPart(nextUrl);
|
||||||
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(queryPart, resourceDef);
|
||||||
|
@ -93,20 +93,6 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
return theNextUrl.substring(paramIndex);
|
return theNextUrl.substring(paramIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: this method probably belongs in a utility class, not here
|
|
||||||
*
|
|
||||||
* @throws DataFormatException If the resource type is not known
|
|
||||||
*/
|
|
||||||
public static RuntimeResourceDefinition parseUrlResourceType(FhirContext theCtx, String theUrl) throws DataFormatException {
|
|
||||||
int paramIndex = theUrl.indexOf('?');
|
|
||||||
String resourceName = theUrl.substring(0, paramIndex);
|
|
||||||
if (resourceName.contains("/")) {
|
|
||||||
resourceName = resourceName.substring(resourceName.lastIndexOf('/') + 1);
|
|
||||||
}
|
|
||||||
return theCtx.getResourceDefinition(resourceName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
initCacheMap();
|
initCacheMap();
|
||||||
|
@ -120,7 +106,7 @@ public class CacheWarmingSvcImpl implements ICacheWarmingSvc {
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
parseWarmUrlParamPart(next.getUrl());
|
parseWarmUrlParamPart(next.getUrl());
|
||||||
parseUrlResourceType(myCtx, next.getUrl());
|
UrlUtil.parseUrlResourceType(myCtx, next.getUrl());
|
||||||
|
|
||||||
myCacheEntryToNextRefresh.put(next, 0L);
|
myCacheEntryToNextRefresh.put(next, 0L);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,17 +29,17 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
|
||||||
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
|
||||||
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer;
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCanonicalizer;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionStrategyEvaluator;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.parser.DataFormatException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
|
||||||
import ca.uhn.fhir.util.SubscriptionUtil;
|
import ca.uhn.fhir.util.SubscriptionUtil;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import org.hl7.fhir.instance.model.Subscription;
|
import org.hl7.fhir.instance.model.Subscription;
|
||||||
|
@ -96,6 +96,8 @@ public class SubscriptionActivatingInterceptor {
|
||||||
private MatchUrlService myMatchUrlService;
|
private MatchUrlService myMatchUrlService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
@Autowired
|
||||||
|
private SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
|
||||||
|
|
||||||
public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
|
public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
|
||||||
// Grab the value for "Subscription.channel.type" so we can see if this
|
// Grab the value for "Subscription.channel.type" so we can see if this
|
||||||
|
@ -160,7 +162,6 @@ public class SubscriptionActivatingInterceptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
|
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
|
||||||
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||||
IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement());
|
IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement());
|
||||||
|
@ -185,6 +186,36 @@ public class SubscriptionActivatingInterceptor {
|
||||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Hook(Pointcut.OP_PRESTORAGE_RESOURCE_CREATED)
|
||||||
|
public void addStrategyTagCreated(IBaseResource theResource) {
|
||||||
|
if (isSubscription(theResource)) {
|
||||||
|
validateCriteriaAndAddStrategy(theResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Hook(Pointcut.OP_PRESTORAGE_RESOURCE_UPDATED)
|
||||||
|
public void addStrategyTagUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
|
||||||
|
if (isSubscription(theNewResource)) {
|
||||||
|
validateCriteriaAndAddStrategy(theNewResource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO KHS add third type of strategy DISABLED if that subscription type is disabled on this server
|
||||||
|
public void validateCriteriaAndAddStrategy(final IBaseResource theResource) {
|
||||||
|
String criteria = mySubscriptionCanonicalizer.getCriteria(theResource);
|
||||||
|
try {
|
||||||
|
SubscriptionMatchingStrategy strategy = mySubscriptionStrategyEvaluator.determineStrategy(criteria);
|
||||||
|
mySubscriptionCanonicalizer.setMatchingStrategyTag(myFhirContext, theResource, strategy);
|
||||||
|
} catch (InvalidRequestException | DataFormatException e) {
|
||||||
|
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED)
|
||||||
|
public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
|
||||||
|
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
|
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_CREATED)
|
||||||
public void resourceCreated(IBaseResource theResource) {
|
public void resourceCreated(IBaseResource theResource) {
|
||||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||||
|
@ -195,46 +226,31 @@ public class SubscriptionActivatingInterceptor {
|
||||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
|
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hook(Pointcut.OP_PRECOMMIT_RESOURCE_UPDATED)
|
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
|
||||||
public void resourceUpdated(IBaseResource theOldResource, IBaseResource theNewResource) {
|
if (isSubscription(theNewResource)) {
|
||||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
|
private boolean isSubscription(IBaseResource theNewResource) {
|
||||||
submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
|
RuntimeResourceDefinition resourceDefinition = myFhirContext.getResourceDefinition(theNewResource);
|
||||||
|
return ResourceTypeEnum.SUBSCRIPTION.getCode().equals(resourceDefinition.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
private void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||||
IIdType id = theMsg.getId(myFhirContext);
|
|
||||||
if (!id.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (theMsg.getOperationType()) {
|
switch (theMsg.getOperationType()) {
|
||||||
case DELETE:
|
case DELETE:
|
||||||
mySubscriptionRegistry.unregisterSubscription(id);
|
mySubscriptionRegistry.unregisterSubscription(theMsg.getId(myFhirContext));
|
||||||
break;
|
break;
|
||||||
case CREATE:
|
case CREATE:
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
final IBaseResource subscription = theMsg.getNewPayload(myFhirContext);
|
activateAndRegisterSubscriptionIfRequiredInTransaction(theMsg.getNewPayload(myFhirContext));
|
||||||
validateCriteria(subscription);
|
|
||||||
activateAndRegisterSubscriptionIfRequiredInTransaction(subscription);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validateCriteria(final IBaseResource theResource) {
|
|
||||||
CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theResource);
|
|
||||||
String criteria = subscription.getCriteriaString();
|
|
||||||
try {
|
|
||||||
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, criteria);
|
|
||||||
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
|
|
||||||
} catch (InvalidRequestException e) {
|
|
||||||
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
|
private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
||||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||||
|
|
|
@ -27,7 +27,6 @@ import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
|
@ -42,6 +41,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.util.ParametersUtil;
|
import ca.uhn.fhir.util.ParametersUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
import ca.uhn.fhir.util.ValidateUtil;
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
|
@ -209,7 +209,7 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
||||||
// If we don't have an active search started, and one needs to be.. start it
|
// If we don't have an active search started, and one needs to be.. start it
|
||||||
if (isBlank(theJobDetails.getCurrentSearchUuid()) && theJobDetails.getRemainingSearchUrls().size() > 0 && totalSubmitted < myMaxSubmitPerPass) {
|
if (isBlank(theJobDetails.getCurrentSearchUuid()) && theJobDetails.getRemainingSearchUrls().size() > 0 && totalSubmitted < myMaxSubmitPerPass) {
|
||||||
String nextSearchUrl = theJobDetails.getRemainingSearchUrls().remove(0);
|
String nextSearchUrl = theJobDetails.getRemainingSearchUrls().remove(0);
|
||||||
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, nextSearchUrl);
|
RuntimeResourceDefinition resourceDef = UrlUtil.parseUrlResourceType(myFhirContext, nextSearchUrl);
|
||||||
String queryPart = nextSearchUrl.substring(nextSearchUrl.indexOf('?'));
|
String queryPart = nextSearchUrl.substring(nextSearchUrl.indexOf('?'));
|
||||||
String resourceType = resourceDef.getName();
|
String resourceType = resourceDef.getName();
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,8 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
@ -48,7 +48,10 @@ public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMat
|
||||||
SubscriptionMatchResult result;
|
SubscriptionMatchResult result;
|
||||||
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
|
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
|
||||||
result = myInMemorySubscriptionMatcher.match(theSubscription, theMsg);
|
result = myInMemorySubscriptionMatcher.match(theSubscription, theMsg);
|
||||||
if (!result.supported()) {
|
if (result.supported()) {
|
||||||
|
// TODO KHS test
|
||||||
|
result.setInMemory(true);
|
||||||
|
} else {
|
||||||
ourLog.info("Criteria {} for Subscription {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", theSubscription.getCriteriaString(), theSubscription.getIdElementString(), result.getUnsupportedReason());
|
ourLog.info("Criteria {} for Subscription {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", theSubscription.getCriteriaString(), theSubscription.getIdElementString(), result.getUnsupportedReason());
|
||||||
result = myDaoSubscriptionMatcher.match(theSubscription, theMsg);
|
result = myDaoSubscriptionMatcher.match(theSubscription, theMsg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
|
||||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
|
@ -32,7 +31,6 @@ import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -63,13 +61,13 @@ public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
|
||||||
|
|
||||||
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
ourLog.debug("Subscription check found {} results for query: {}", results.size(), criteria);
|
||||||
|
|
||||||
return new SubscriptionMatchResult(results.size() > 0, "DATABASE");
|
return SubscriptionMatchResult.fromBoolean(results.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Search based on a query criteria
|
* Search based on a query criteria
|
||||||
*/
|
*/
|
||||||
protected IBundleProvider performSearch(String theCriteria) {
|
private IBundleProvider performSearch(String theCriteria) {
|
||||||
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||||
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
RuntimeResourceDefinition responseResourceDef = subscriptionDao.validateCriteriaAndReturnResourceDefinition(theCriteria);
|
||||||
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|
||||||
import ca.uhn.fhir.rest.param.*;
|
import ca.uhn.fhir.rest.param.*;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -36,28 +35,41 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
@Autowired
|
@Autowired
|
||||||
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
||||||
@Autowired
|
@Autowired
|
||||||
|
SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
|
||||||
|
@Autowired
|
||||||
FhirContext myContext;
|
FhirContext myContext;
|
||||||
|
|
||||||
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
|
private void assertMatched(Resource resource, SearchParameterMap params) {
|
||||||
String criteria = params.toNormalizedQueryString(myContext);
|
|
||||||
ourLog.info("Criteria: <{}>", criteria);
|
|
||||||
return myInMemorySubscriptionMatcher.match(criteria, resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
|
|
||||||
assertFalse(match(resource, params).supported());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertMatched(IBaseResource resource, SearchParameterMap params) {
|
|
||||||
SubscriptionMatchResult result = match(resource, params);
|
SubscriptionMatchResult result = match(resource, params);
|
||||||
assertTrue(result.getUnsupportedReason(), result.supported());
|
assertTrue(result.getUnsupportedReason(), result.supported());
|
||||||
assertTrue(result.matched());
|
assertTrue(result.matched());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNotMatched(IBaseResource resource, SearchParameterMap params) {
|
private void assertNotMatched(Resource resource, SearchParameterMap params) {
|
||||||
SubscriptionMatchResult result = match(resource, params);
|
SubscriptionMatchResult result = match(resource, params);
|
||||||
assertTrue(result.getUnsupportedReason(), result.supported());
|
assertTrue(result.getUnsupportedReason(), result.supported());
|
||||||
assertFalse(result.matched());
|
assertFalse(result.matched());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, params)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubscriptionMatchResult match(Resource theResource, SearchParameterMap theParams) {
|
||||||
|
return match(getCriteria(theResource, theParams), theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCriteria(Resource theResource, SearchParameterMap theParams) {
|
||||||
|
return theResource.getResourceType().name() + theParams.toNormalizedQueryString(myContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SubscriptionMatchResult match(String criteria, Resource theResource) {
|
||||||
|
ourLog.info("Criteria: <{}>", criteria);
|
||||||
|
return myInMemorySubscriptionMatcher.match(criteria, theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertUnsupported(Resource resource, SearchParameterMap theParams) {
|
||||||
|
SubscriptionMatchResult result = match(resource, theParams);
|
||||||
|
assertFalse(result.supported());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(getCriteria(resource, theParams)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -93,7 +105,6 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
|
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
|
params.add("_has", new HasParam("Observation", "subject", "identifier", "urn:system|FOO"));
|
||||||
String criteria = params.toNormalizedQueryString(myContext);
|
|
||||||
assertUnsupported(patient, params);
|
assertUnsupported(patient, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -130,7 +141,7 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
|
|
||||||
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01");
|
TokenParam v0 = new TokenParam("foo", "testSearchCompositeParamN01");
|
||||||
StringParam v1 = new StringParam("testSearchCompositeParamS01");
|
StringParam v1 = new StringParam("testSearchCompositeParamS01");
|
||||||
CompositeParam<TokenParam, StringParam> val = new CompositeParam<TokenParam, StringParam>(v0, v1);
|
CompositeParam<TokenParam, StringParam> val = new CompositeParam<>(v0, v1);
|
||||||
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val);
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Observation.SP_CODE_VALUE_STRING, val);
|
||||||
assertUnsupported(o1, params);
|
assertUnsupported(o1, params);
|
||||||
}
|
}
|
||||||
|
@ -170,11 +181,11 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdNotSupported() {
|
public void testIdSupported() {
|
||||||
Observation o1 = new Observation();
|
Observation o1 = new Observation();
|
||||||
SearchParameterMap params = new SearchParameterMap();
|
SearchParameterMap params = new SearchParameterMap();
|
||||||
params.add("_id", new StringParam("testSearchForUnknownAlphanumericId"));
|
params.add("_id", new StringParam("testSearchForUnknownAlphanumericId"));
|
||||||
assertUnsupported(o1, params);
|
assertNotMatched(o1, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -190,7 +201,7 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchLastUpdatedParamUnsupported() throws InterruptedException {
|
public void testSearchLastUpdatedParamUnsupported() {
|
||||||
String methodName = "testSearchLastUpdatedParam";
|
String methodName = "testSearchLastUpdatedParam";
|
||||||
DateTimeType today = new DateTimeType(new Date(), TemporalPrecisionEnum.DAY);
|
DateTimeType today = new DateTimeType(new Date(), TemporalPrecisionEnum.DAY);
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
|
@ -294,12 +305,12 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
@Test
|
@Test
|
||||||
public void testSearchQuantityWrongParam() {
|
public void testSearchQuantityWrongParam() {
|
||||||
Condition c1 = new Condition();
|
Condition c1 = new Condition();
|
||||||
c1.setAbatement(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L)));
|
c1.setAbatement(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
|
||||||
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ABATEMENT_AGE, new QuantityParam("1"));
|
SearchParameterMap params = new SearchParameterMap().setLoadSynchronous(true).add(Condition.SP_ABATEMENT_AGE, new QuantityParam("1"));
|
||||||
assertMatched(c1, params);
|
assertMatched(c1, params);
|
||||||
|
|
||||||
Condition c2 = new Condition();
|
Condition c2 = new Condition();
|
||||||
c2.setOnset(new Range().setLow((SimpleQuantity) new SimpleQuantity().setValue(1L)).setHigh((SimpleQuantity) new SimpleQuantity().setValue(1L)));
|
c2.setOnset(new Range().setLow(new SimpleQuantity().setValue(1L)).setHigh(new SimpleQuantity().setValue(1L)));
|
||||||
|
|
||||||
params = new SearchParameterMap().add(Condition.SP_ONSET_AGE, new QuantityParam("1"));
|
params = new SearchParameterMap().add(Condition.SP_ONSET_AGE, new QuantityParam("1"));
|
||||||
assertMatched(c2, params);
|
assertMatched(c2, params);
|
||||||
|
@ -414,7 +425,7 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchStringParam() throws Exception {
|
public void testSearchStringParam() {
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
|
patient.addName().setFamily("Tester_testSearchStringParam").addGiven("Joe");
|
||||||
|
@ -569,13 +580,11 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchTokenWithNotModifierUnsupported() {
|
public void testSearchTokenWithNotModifierUnsupported() {
|
||||||
String male, female;
|
|
||||||
Patient patient = new Patient();
|
Patient patient = new Patient();
|
||||||
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
patient.addIdentifier().setSystem("urn:system").setValue("001");
|
||||||
patient.addName().setFamily("Tester").addGiven("Joe");
|
patient.addName().setFamily("Tester").addGiven("Joe");
|
||||||
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
patient.setGender(Enumerations.AdministrativeGender.MALE);
|
||||||
|
|
||||||
List<String> patients;
|
|
||||||
SearchParameterMap params;
|
SearchParameterMap params;
|
||||||
|
|
||||||
params = new SearchParameterMap();
|
params = new SearchParameterMap();
|
||||||
|
@ -640,7 +649,6 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
o2.setValue(q2);
|
o2.setValue(q2);
|
||||||
|
|
||||||
SearchParameterMap map;
|
SearchParameterMap map;
|
||||||
IBundleProvider found;
|
|
||||||
QuantityParam param;
|
QuantityParam param;
|
||||||
|
|
||||||
map = new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
|
@ -682,9 +690,7 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
Patient pt1 = new Patient();
|
Patient pt1 = new Patient();
|
||||||
pt1.addName().setFamily("ABCDEFGHIJK");
|
pt1.addName().setFamily("ABCDEFGHIJK");
|
||||||
|
|
||||||
List<String> ids;
|
|
||||||
SearchParameterMap map;
|
SearchParameterMap map;
|
||||||
IBundleProvider results;
|
|
||||||
|
|
||||||
// Contains = true
|
// Contains = true
|
||||||
map = new SearchParameterMap();
|
map = new SearchParameterMap();
|
||||||
|
@ -875,7 +881,7 @@ public class InMemorySubscriptionMatcherTestR4 {
|
||||||
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));
|
map.add(Observation.SP_DATE, new DateParam("2011-01-02"));
|
||||||
|
|
||||||
for (Observation obs : nlist) {
|
for (Observation obs : nlist) {
|
||||||
// assertNotMatched(obs, map);
|
assertNotMatched(obs, map);
|
||||||
}
|
}
|
||||||
for (Observation obs : ylist) {
|
for (Observation obs : ylist) {
|
||||||
ourLog.info("Obs {} has time {}", obs.getId(), obs.getEffectiveDateTimeType().getValue().toString());
|
ourLog.info("Obs {} has time {}", obs.getId(), obs.getEffectiveDateTimeType().getValue().toString());
|
||||||
|
|
|
@ -6,6 +6,8 @@ import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
|
import ca.uhn.fhir.jpa.subscription.NotificationServlet;
|
||||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Update;
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
|
@ -106,12 +108,11 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
subscription.setChannel(channel);
|
subscription.setChannel(channel);
|
||||||
|
|
||||||
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
|
||||||
subscription.setId(methodOutcome.getId().getIdPart());
|
|
||||||
mySubscriptionIds.add(methodOutcome.getId());
|
mySubscriptionIds.add(methodOutcome.getId());
|
||||||
|
|
||||||
waitForQueueToDrain();
|
waitForQueueToDrain();
|
||||||
|
|
||||||
return subscription;
|
return (Subscription)methodOutcome.getResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observation sendObservation(String code, String system) {
|
private Observation sendObservation(String code, String system) {
|
||||||
|
@ -352,7 +353,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(5, ourUpdatedObservations);
|
waitForSize(5, ourUpdatedObservations);
|
||||||
|
|
||||||
Assert.assertFalse(subscription1.getId().equals(subscription2.getId()));
|
Assert.assertNotEquals(subscription1.getId(), subscription2.getId());
|
||||||
Assert.assertFalse(observation1.getId().isEmpty());
|
Assert.assertFalse(observation1.getId().isEmpty());
|
||||||
Assert.assertFalse(observation2.getId().isEmpty());
|
Assert.assertFalse(observation2.getId().isEmpty());
|
||||||
}
|
}
|
||||||
|
@ -414,6 +415,58 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
||||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubscriptionActivatesInMemoryTag() throws Exception {
|
||||||
|
String payload = "application/fhir+xml";
|
||||||
|
|
||||||
|
String code = "1000000050";
|
||||||
|
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||||
|
|
||||||
|
Subscription subscriptionOrig = createSubscription(criteria1, payload, ourListenerServerBase);
|
||||||
|
IdType subscriptionId = subscriptionOrig.getIdElement();
|
||||||
|
|
||||||
|
assertEquals(Subscription.SubscriptionStatus.REQUESTED, subscriptionOrig.getStatus());
|
||||||
|
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
||||||
|
assertEquals(1, tags.size());
|
||||||
|
Coding tag = tags.get(0);
|
||||||
|
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
|
||||||
|
assertEquals("In-memory", tag.getDisplay());
|
||||||
|
|
||||||
|
Subscription subscriptionActivated = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
|
||||||
|
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscriptionActivated.getStatus());
|
||||||
|
tags = subscriptionActivated.getMeta().getTag();
|
||||||
|
assertEquals(1, tags.size());
|
||||||
|
tag = tags.get(0);
|
||||||
|
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY.toString(), tag.getCode());
|
||||||
|
assertEquals("In-memory", tag.getDisplay());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubscriptionActivatesDatabaseTag() throws Exception {
|
||||||
|
String payload = "application/fhir+xml";
|
||||||
|
|
||||||
|
Subscription subscriptionOrig = createSubscription("Observation?code=17861-6&context.type=IHD", payload, ourListenerServerBase);
|
||||||
|
IdType subscriptionId = subscriptionOrig.getIdElement();
|
||||||
|
|
||||||
|
List<Coding> tags = subscriptionOrig.getMeta().getTag();
|
||||||
|
assertEquals(1, tags.size());
|
||||||
|
Coding tag = tags.get(0);
|
||||||
|
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
|
||||||
|
assertEquals("Database", tag.getDisplay());
|
||||||
|
|
||||||
|
Subscription subscription = ourClient.read().resource(Subscription.class).withId(subscriptionId.toUnqualifiedVersionless()).execute();
|
||||||
|
assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus());
|
||||||
|
tags = subscription.getMeta().getTag();
|
||||||
|
assertEquals(1, tags.size());
|
||||||
|
tag = tags.get(0);
|
||||||
|
assertEquals(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY, tag.getSystem());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.DATABASE.toString(), tag.getCode());
|
||||||
|
assertEquals("Database", tag.getDisplay());
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void startListenerServer() throws Exception {
|
public static void startListenerServer() throws Exception {
|
||||||
ourListenerPort = PortUtil.findFreePort();
|
ourListenerPort = PortUtil.findFreePort();
|
||||||
|
|
|
@ -86,7 +86,7 @@ public enum Pointcut {
|
||||||
SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED("ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription"),
|
SUBSCRIPTION_AFTER_ACTIVE_SUBSCRIPTION_REGISTERED("ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoked before a resource will be updated, immediately before the resource
|
* Invoked before a resource will be created, immediately before the resource
|
||||||
* is persisted to the database.
|
* is persisted to the database.
|
||||||
* <p>
|
* <p>
|
||||||
* Hooks will have access to the contents of the resource being created
|
* Hooks will have access to the contents of the resource being created
|
||||||
|
|
|
@ -24,12 +24,14 @@ import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchingStrategy;
|
||||||
import ca.uhn.fhir.model.api.ExtensionDt;
|
import ca.uhn.fhir.model.api.ExtensionDt;
|
||||||
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||||
import org.hl7.fhir.dstu3.model.Subscription;
|
import org.hl7.fhir.dstu3.model.Subscription;
|
||||||
import org.hl7.fhir.exceptions.FHIRException;
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.r4.model.Extension;
|
import org.hl7.fhir.r4.model.Extension;
|
||||||
|
@ -261,4 +263,33 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCriteria(IBaseResource theSubscription) {
|
||||||
|
switch (myFhirContext.getVersion().getVersion()) {
|
||||||
|
case DSTU2:
|
||||||
|
return ((ca.uhn.fhir.model.dstu2.resource.Subscription)theSubscription).getCriteria();
|
||||||
|
case DSTU3:
|
||||||
|
return ((org.hl7.fhir.dstu3.model.Subscription)theSubscription).getCriteria();
|
||||||
|
case R4:
|
||||||
|
return ((org.hl7.fhir.r4.model.Subscription)theSubscription).getCriteria();
|
||||||
|
default:
|
||||||
|
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setMatchingStrategyTag(FhirContext theFhirContext, IBaseResource theSubscription, SubscriptionMatchingStrategy theStrategy) {
|
||||||
|
IBaseMetaType meta = theSubscription.getMeta();
|
||||||
|
String value = theStrategy.toString();
|
||||||
|
String display;
|
||||||
|
|
||||||
|
if (theStrategy == SubscriptionMatchingStrategy.DATABASE) {
|
||||||
|
display = "Database";
|
||||||
|
} else if (theStrategy == SubscriptionMatchingStrategy.IN_MEMORY) {
|
||||||
|
display = "In-memory";
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unknown " + SubscriptionMatchingStrategy.class.getSimpleName() + ": "+theStrategy);
|
||||||
|
}
|
||||||
|
meta.addTag().setSystem(SubscriptionConstants.EXT_SUBSCRIPTION_MATCHING_STRATEGY).setCode(value).setDisplay(display);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,13 @@ public class SubscriptionConstants {
|
||||||
*/
|
*/
|
||||||
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
|
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicate which strategy will be used to match this subscription
|
||||||
|
*/
|
||||||
|
|
||||||
|
public static final String EXT_SUBSCRIPTION_MATCHING_STRATEGY = "http://hapifhir.io/fhir/StructureDefinition/subscription-matching-strategy";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of threads used in subscription channel processing
|
* The number of threads used in subscription channel processing
|
||||||
*/
|
*/
|
||||||
|
@ -79,12 +86,8 @@ public class SubscriptionConstants {
|
||||||
public static final int MAX_SUBSCRIPTION_RESULTS = 1000;
|
public static final int MAX_SUBSCRIPTION_RESULTS = 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of the queue used for sending resources to the subscription matching processor
|
* The size of the queue used for sending resources to the subscription matching processor and by each subscription delivery queue
|
||||||
*/
|
*/
|
||||||
public static final int PROCESSING_EXECUTOR_QUEUE_SIZE = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The size of the queue used by each subscription delivery queue
|
|
||||||
*/
|
|
||||||
public static final int DELIVERY_EXECUTOR_QUEUE_SIZE = 1000;
|
public static final int DELIVERY_EXECUTOR_QUEUE_SIZE = 1000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import ca.uhn.fhir.rest.param.BaseParamWithPrefix;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||||
import ca.uhn.fhir.rest.param.StringParam;
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
@ -54,79 +55,93 @@ public class CriteriaResourceMatcher {
|
||||||
@Autowired
|
@Autowired
|
||||||
FhirContext myFhirContext;
|
FhirContext myFhirContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called in two different scenarios. With a null theResource, it determines whether database matching might be required.
|
||||||
|
* Otherwise, it tries to perform the match in-memory, returning UNSUPPORTED if it's not possible.
|
||||||
|
*
|
||||||
|
* Note that there will be cases where it returns UNSUPPORTED with a null resource, but when a non-null resource it returns supported and no match.
|
||||||
|
* This is because an earlier parameter may be matchable in-memory in which case processing stops and we never get to the parameter
|
||||||
|
* that would have required a database call.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
public SubscriptionMatchResult match(String theCriteria, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) {
|
public SubscriptionMatchResult match(String theCriteria, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) {
|
||||||
|
RuntimeResourceDefinition resourceDefinition;
|
||||||
|
if (theResource == null) {
|
||||||
|
resourceDefinition = UrlUtil.parseUrlResourceType(myFhirContext, theCriteria);
|
||||||
|
} else {
|
||||||
|
resourceDefinition = myFhirContext.getResourceDefinition(theResource);
|
||||||
|
}
|
||||||
SearchParameterMap searchParameterMap;
|
SearchParameterMap searchParameterMap;
|
||||||
try {
|
try {
|
||||||
searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, myFhirContext.getResourceDefinition(theResource));
|
searchParameterMap = myMatchUrlService.translateMatchUrl(theCriteria, resourceDefinition);
|
||||||
} catch (UnsupportedOperationException e) {
|
} catch (UnsupportedOperationException e) {
|
||||||
return new SubscriptionMatchResult(theCriteria, CRITERIA);
|
return SubscriptionMatchResult.unsupportedFromReason(SubscriptionMatchResult.PARSE_FAIL);
|
||||||
}
|
}
|
||||||
searchParameterMap.clean();
|
searchParameterMap.clean();
|
||||||
if (searchParameterMap.getLastUpdated() != null) {
|
if (searchParameterMap.getLastUpdated() != null) {
|
||||||
return new SubscriptionMatchResult(Constants.PARAM_LASTUPDATED, "Standard Parameters not supported");
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(Constants.PARAM_LASTUPDATED, SubscriptionMatchResult.STANDARD_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
|
for (Map.Entry<String, List<List<? extends IQueryParameterType>>> entry : searchParameterMap.entrySet()) {
|
||||||
String theParamName = entry.getKey();
|
String theParamName = entry.getKey();
|
||||||
List<List<? extends IQueryParameterType>> theAndOrParams = entry.getValue();
|
List<List<? extends IQueryParameterType>> theAndOrParams = entry.getValue();
|
||||||
SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, theResource, theSearchParams);
|
SubscriptionMatchResult result = matchIdsWithAndOr(theParamName, theAndOrParams, resourceDefinition, theResource, theSearchParams);
|
||||||
if (!result.matched()){
|
if (!result.matched()){
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new SubscriptionMatchResult(true, CRITERIA);
|
return SubscriptionMatchResult.successfulMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is modelled from SearchBuilder.searchForIdsWithAndOr()
|
// This method is modelled from SearchBuilder.searchForIdsWithAndOr()
|
||||||
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) {
|
private SubscriptionMatchResult matchIdsWithAndOr(String theParamName, List<List<? extends IQueryParameterType>> theAndOrParams, RuntimeResourceDefinition theResourceDefinition, IBaseResource theResource, ResourceIndexedSearchParams theSearchParams) {
|
||||||
if (theAndOrParams.isEmpty()) {
|
if (theAndOrParams.isEmpty()) {
|
||||||
return new SubscriptionMatchResult(true, CRITERIA);
|
return SubscriptionMatchResult.successfulMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasQualifiers(theAndOrParams)) {
|
if (hasQualifiers(theAndOrParams)) {
|
||||||
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.STANDARD_PARAMETER);
|
||||||
return new SubscriptionMatchResult(theParamName, "Standard Parameters not supported.");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
if (hasPrefixes(theAndOrParams)) {
|
if (hasPrefixes(theAndOrParams)) {
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName, "Prefixes not supported.");
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PREFIX);
|
||||||
|
|
||||||
}
|
}
|
||||||
if (hasChain(theAndOrParams)) {
|
if (hasChain(theAndOrParams)) {
|
||||||
return new SubscriptionMatchResult(theParamName, "Chained references are not supported");
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.CHAIN);
|
||||||
}
|
}
|
||||||
switch (theParamName) {
|
switch (theParamName) {
|
||||||
case IAnyResource.SP_RES_ID:
|
case IAnyResource.SP_RES_ID:
|
||||||
|
|
||||||
return new SubscriptionMatchResult(matchIdsAndOr(theAndOrParams, theResource), CRITERIA);
|
return SubscriptionMatchResult.fromBoolean(matchIdsAndOr(theAndOrParams, theResource));
|
||||||
|
|
||||||
case IAnyResource.SP_RES_LANGUAGE:
|
case IAnyResource.SP_RES_LANGUAGE:
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
|
||||||
|
|
||||||
case Constants.PARAM_HAS:
|
case Constants.PARAM_HAS:
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
|
||||||
|
|
||||||
case Constants.PARAM_TAG:
|
case Constants.PARAM_TAG:
|
||||||
case Constants.PARAM_PROFILE:
|
case Constants.PARAM_PROFILE:
|
||||||
case Constants.PARAM_SECURITY:
|
case Constants.PARAM_SECURITY:
|
||||||
|
|
||||||
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
||||||
String resourceName = myFhirContext.getResourceDefinition(theResource).getName();
|
String resourceName = theResourceDefinition.getName();
|
||||||
RuntimeSearchParam paramDef = mySearchParamRegistry.getActiveSearchParam(resourceName, theParamName);
|
RuntimeSearchParam paramDef = mySearchParamRegistry.getActiveSearchParam(resourceName, theParamName);
|
||||||
return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef);
|
return matchResourceParam(theParamName, theAndOrParams, theSearchParams, resourceName, paramDef);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean matchIdsAndOr(List<List<? extends IQueryParameterType>> theAndOrParams, IBaseResource theResource) {
|
private boolean matchIdsAndOr(List<List<? extends IQueryParameterType>> theAndOrParams, IBaseResource theResource) {
|
||||||
|
if (theResource == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource));
|
return theAndOrParams.stream().allMatch(nextAnd -> matchIdsOr(nextAnd, theResource));
|
||||||
}
|
}
|
||||||
private boolean matchIdsOr(List<? extends IQueryParameterType> theOrParams, IBaseResource theResource) {
|
private boolean matchIdsOr(List<? extends IQueryParameterType> theOrParams, IBaseResource theResource) {
|
||||||
|
if (theResource == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return theOrParams.stream().anyMatch(param -> param instanceof StringParam && matchId(((StringParam)param).getValue(), theResource.getIdElement()));
|
return theOrParams.stream().anyMatch(param -> param instanceof StringParam && matchId(((StringParam)param).getValue(), theResource.getIdElement()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,16 +159,20 @@ public class CriteriaResourceMatcher {
|
||||||
case URI:
|
case URI:
|
||||||
case DATE:
|
case DATE:
|
||||||
case REFERENCE:
|
case REFERENCE:
|
||||||
return new SubscriptionMatchResult(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)), CRITERIA);
|
if (theSearchParams == null) {
|
||||||
|
return SubscriptionMatchResult.successfulMatch();
|
||||||
|
} else {
|
||||||
|
return SubscriptionMatchResult.fromBoolean(theAndOrParams.stream().anyMatch(nextAnd -> matchParams(theResourceName, theParamName, theParamDef, nextAnd, theSearchParams)));
|
||||||
|
}
|
||||||
case COMPOSITE:
|
case COMPOSITE:
|
||||||
case HAS:
|
case HAS:
|
||||||
case SPECIAL:
|
case SPECIAL:
|
||||||
default:
|
default:
|
||||||
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
|
if (Constants.PARAM_CONTENT.equals(theParamName) || Constants.PARAM_TEXT.equals(theParamName)) {
|
||||||
return new SubscriptionMatchResult(theParamName, CRITERIA);
|
return SubscriptionMatchResult.unsupportedFromParameterAndReason(theParamName, SubscriptionMatchResult.PARAM);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName);
|
throw new InvalidRequestException("Unknown search parameter " + theParamName + " for resource type " + theResourceName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,26 +21,47 @@ package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SubscriptionMatchResult {
|
public class SubscriptionMatchResult {
|
||||||
|
public static final String PARSE_FAIL = "Failed to translate parse query string";
|
||||||
|
public static final String STANDARD_PARAMETER = "Standard parameters not supported";
|
||||||
|
public static final String PREFIX = "Prefixes not supported";
|
||||||
|
public static final String CHAIN = "Chained references are not supported";
|
||||||
|
public static final String PARAM = "Param not supported";
|
||||||
|
|
||||||
private final boolean myMatch;
|
private final boolean myMatch;
|
||||||
private final boolean mySupported;
|
private final boolean mySupported;
|
||||||
private final String myUnsupportedParameter;
|
private final String myUnsupportedParameter;
|
||||||
private final String myUnsupportedReason;
|
private final String myUnsupportedReason;
|
||||||
private final String myMatcherShortName;
|
|
||||||
|
|
||||||
public SubscriptionMatchResult(boolean theMatch, String theMatcherShortName) {
|
private boolean myInMemory = false;
|
||||||
|
|
||||||
|
private SubscriptionMatchResult(boolean theMatch) {
|
||||||
this.myMatch = theMatch;
|
this.myMatch = theMatch;
|
||||||
this.mySupported = true;
|
this.mySupported = true;
|
||||||
this.myUnsupportedParameter = null;
|
this.myUnsupportedParameter = null;
|
||||||
this.myUnsupportedReason = null;
|
this.myUnsupportedReason = null;
|
||||||
this.myMatcherShortName = theMatcherShortName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubscriptionMatchResult(String theUnsupportedParameter, String theMatcherShortName) {
|
private SubscriptionMatchResult(String theUnsupportedParameter, String theUnsupportedReason) {
|
||||||
this.myMatch = false;
|
this.myMatch = false;
|
||||||
this.mySupported = false;
|
this.mySupported = false;
|
||||||
this.myUnsupportedParameter = theUnsupportedParameter;
|
this.myUnsupportedParameter = theUnsupportedParameter;
|
||||||
this.myUnsupportedReason = "Parameter not supported";
|
this.myUnsupportedReason = theUnsupportedReason;
|
||||||
this.myMatcherShortName = theMatcherShortName;
|
}
|
||||||
|
|
||||||
|
public static SubscriptionMatchResult successfulMatch() {
|
||||||
|
return new SubscriptionMatchResult(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubscriptionMatchResult fromBoolean(boolean theMatched) {
|
||||||
|
return new SubscriptionMatchResult(theMatched);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubscriptionMatchResult unsupportedFromReason(String theUnsupportedReason) {
|
||||||
|
return new SubscriptionMatchResult(null, theUnsupportedReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SubscriptionMatchResult unsupportedFromParameterAndReason(String theUnsupportedParameter, String theUnsupportedReason) {
|
||||||
|
return new SubscriptionMatchResult(theUnsupportedParameter, theUnsupportedReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supported() {
|
public boolean supported() {
|
||||||
|
@ -52,14 +73,17 @@ public class SubscriptionMatchResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUnsupportedReason() {
|
public String getUnsupportedReason() {
|
||||||
return "Parameter: <" + myUnsupportedParameter + "> Reason: " + myUnsupportedReason;
|
if (myUnsupportedParameter != null) {
|
||||||
|
return "Parameter: <" + myUnsupportedParameter + "> Reason: " + myUnsupportedReason;
|
||||||
|
}
|
||||||
|
return myUnsupportedReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean isInMemory() {
|
||||||
* Returns a short name of the matcher that generated this
|
return myInMemory;
|
||||||
* response, for use in logging
|
}
|
||||||
*/
|
|
||||||
public String matcherShortName() {
|
public void setInMemory(boolean theInMemory) {
|
||||||
return myMatcherShortName;
|
myInMemory = theInMemory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
|
|
||||||
|
public enum SubscriptionMatchingStrategy {
|
||||||
|
/**
|
||||||
|
* Resources can be matched against this subcription in-memory without needing to make a call out to a FHIR Repository
|
||||||
|
*/
|
||||||
|
IN_MEMORY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resources cannot be matched against this subscription in-memory. We need to make a call to a FHIR Repository to determine a match
|
||||||
|
*/
|
||||||
|
DATABASE
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SubscriptionStrategyEvaluator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CriteriaResourceMatcher myCriteriaResourceMatcher;
|
||||||
|
|
||||||
|
public SubscriptionMatchingStrategy determineStrategy(String theCriteria) {
|
||||||
|
SubscriptionMatchResult result = myCriteriaResourceMatcher.match(theCriteria, null, null);
|
||||||
|
if (result.supported()) {
|
||||||
|
return SubscriptionMatchingStrategy.IN_MEMORY;
|
||||||
|
}
|
||||||
|
return SubscriptionMatchingStrategy.DATABASE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -117,8 +117,10 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
if (!matchResult.matched()) {
|
if (!matchResult.matched()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
ourLog.debug("Subscription {} was matched by resource {} {}",
|
||||||
ourLog.info("Subscription {} was matched by resource {} using matcher {}", nextActiveSubscription.getSubscription().getIdElement(myFhirContext).getValue(), resourceId.toUnqualifiedVersionless().getValue(), matchResult.matcherShortName());
|
nextActiveSubscription.getSubscription().getIdElement(myFhirContext).getValue(),
|
||||||
|
resourceId.toUnqualifiedVersionless().getValue(),
|
||||||
|
matchResult.isInMemory() ? "in-memory" : "by querying the repository");
|
||||||
|
|
||||||
IBaseResource payload = theMsg.getNewPayload(myFhirContext);
|
IBaseResource payload = theMsg.getNewPayload(myFhirContext);
|
||||||
|
|
||||||
|
@ -161,7 +163,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
criteriaResource = criteriaResource.substring(0, criteriaResource.indexOf("?"));
|
criteriaResource = criteriaResource.substring(0, criteriaResource.indexOf("?"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceType != null && criteriaString != null && !criteriaResource.equals(resourceType)) {
|
if (resourceType != null && !criteriaResource.equals(resourceType)) {
|
||||||
ourLog.trace("Skipping subscription search for {} because it does not match the criteria {}", resourceType, criteriaString);
|
ourLog.trace("Skipping subscription search for {} because it does not match the criteria {}", resourceType, criteriaString);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,22 @@ import org.hl7.fhir.dstu3.model.*;
|
||||||
import org.hl7.fhir.dstu3.model.codesystems.MedicationRequestCategory;
|
import org.hl7.fhir.dstu3.model.codesystems.MedicationRequestCategory;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test {
|
public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test {
|
||||||
|
@Autowired
|
||||||
|
SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
|
||||||
@Autowired
|
@Autowired
|
||||||
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
||||||
|
|
||||||
private void assertUnsupported(IBaseResource resource, String criteria) {
|
private void assertUnsupported(IBaseResource resource, String criteria) {
|
||||||
assertFalse(myInMemorySubscriptionMatcher.match(criteria, resource).supported());
|
assertFalse(myInMemorySubscriptionMatcher.match(criteria, resource).supported());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(criteria));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertMatched(IBaseResource resource, String criteria) {
|
private void assertMatched(IBaseResource resource, String criteria) {
|
||||||
|
@ -30,13 +30,20 @@ public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test
|
||||||
|
|
||||||
assertTrue(result.supported());
|
assertTrue(result.supported());
|
||||||
assertTrue(result.matched());
|
assertTrue(result.matched());
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(criteria));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNotMatched(IBaseResource resource, String criteria) {
|
private void assertNotMatched(IBaseResource resource, String criteria) {
|
||||||
|
assertNotMatched(resource, criteria, SubscriptionMatchingStrategy.IN_MEMORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNotMatched(IBaseResource resource, String criteria, SubscriptionMatchingStrategy theSubscriptionMatchingStrategy) {
|
||||||
SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource);
|
SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource);
|
||||||
|
|
||||||
assertTrue(result.supported());
|
assertTrue(result.supported());
|
||||||
assertFalse(result.matched());
|
assertFalse(result.matched());
|
||||||
|
|
||||||
|
assertEquals(theSubscriptionMatchingStrategy, mySubscriptionStrategyEvaluator.determineStrategy(criteria));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,7 +159,7 @@ public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test
|
||||||
{
|
{
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
obs.getCode().addCoding().setCode("XXX");
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
assertNotMatched(obs, criteria);
|
assertNotMatched(obs, criteria, SubscriptionMatchingStrategy.DATABASE);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
|
@ -168,7 +175,7 @@ public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test
|
||||||
{
|
{
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
obs.getCode().addCoding().setCode("XXX");
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
assertNotMatched(obs, criteria);
|
assertNotMatched(obs, criteria, SubscriptionMatchingStrategy.DATABASE);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
|
@ -266,7 +273,7 @@ public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test
|
||||||
{
|
{
|
||||||
Observation obs = new Observation();
|
Observation obs = new Observation();
|
||||||
obs.getCode().addCoding().setCode("XXX");
|
obs.getCode().addCoding().setCode("XXX");
|
||||||
assertNotMatched(obs, criteria);
|
assertNotMatched(obs, criteria, SubscriptionMatchingStrategy.DATABASE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.matchers.JUnitMatchers.containsString;
|
||||||
|
|
||||||
|
public class SubscriptionStrategyEvaluatorTest extends BaseSubscriptionDstu3Test {
|
||||||
|
@Autowired
|
||||||
|
SubscriptionStrategyEvaluator mySubscriptionStrategyEvaluator;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException exception = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInMemory() {
|
||||||
|
assertInMemory("Observation?");
|
||||||
|
assertInMemory("QuestionnaireResponse?questionnaire=HomeAbsenceHospitalizationRecord,ARIncenterAbsRecord");
|
||||||
|
assertInMemory("CommunicationRequest?occurrence==2018-10-17");
|
||||||
|
assertInMemory("ProcedureRequest?intent=original-order");
|
||||||
|
assertInMemory("MedicationRequest?intent=instance-order&category=outpatient&date==2018-10-19");
|
||||||
|
assertInMemory("MedicationRequest?intent=plan&category=outpatient&status=suspended,entered-in-error,cancelled,stopped");
|
||||||
|
assertDatabase("Observation?code=FR_Org1Blood2nd,FR_Org1Blood3rd,FR_Org%201BldCult,FR_Org2Blood2nd,FR_Org2Blood3rd,FR_Org%202BldCult,FR_Org3Blood2nd,FR_Org3Blood3rd,FR_Org3BldCult,FR_Org4Blood2nd,FR_Org4Blood3rd,FR_Org4BldCult,FR_Org5Blood2nd,FR_Org5Blood3rd,FR_Org%205BldCult,FR_Org6Blood2nd,FR_Org6Blood3rd,FR_Org6BldCult,FR_Org7Blood2nd,FR_Org7Blood3rd,FR_Org7BldCult,FR_Org8Blood2nd,FR_Org8Blood3rd,FR_Org8BldCult,FR_Org9Blood2nd,FR_Org9Blood3rd,FR_Org9BldCult,FR_Bld2ndCulture,FR_Bld3rdCulture,FR_Blood%20Culture,FR_Com1Bld3rd,FR_Com1BldCult,FR_Com2Bld2nd,FR_Com2Bld3rd,FR_Com2BldCult,FR_CultureBld2nd,FR_CultureBld3rd,FR_CultureBldCul,FR_GmStainBldCul,FR_GramStain2Bld,FR_GramStain3Bld,FR_GramStNegBac&context.type=IHD");
|
||||||
|
assertInMemory("Procedure?category=Hemodialysis");
|
||||||
|
assertInMemory("Procedure?code=HD_Standard&status=completed&location=Lab123");
|
||||||
|
assertInMemory("Procedure?code=HD_Standard&status=completed");
|
||||||
|
assertInMemory("QuestionnaireResponse?questionnaire=HomeAbsenceHospitalizationRecord,ARIncenterAbsRecord,FMCSWDepressionSymptomsScreener,FMCAKIComprehensiveSW,FMCSWIntensiveScreener,FMCESRDComprehensiveSW,FMCNutritionProgressNote,FMCAKIComprehensiveRN");
|
||||||
|
assertInMemory("EpisodeOfCare?status=active");
|
||||||
|
assertInMemory("Observation?code=111111111&_format=xml");
|
||||||
|
assertInMemory("Observation?code=SNOMED-CT|123&_format=xml");
|
||||||
|
|
||||||
|
assertDatabase("Observation?code=17861-6&context.type=IHD");
|
||||||
|
assertDatabase("Observation?context.type=IHD&code=17861-6");
|
||||||
|
|
||||||
|
exception.expect(InvalidRequestException.class);
|
||||||
|
exception.expectMessage(containsString("Resource type Observation does not have a parameter with name: codeee"));
|
||||||
|
assertInMemory("Observation?codeee=SNOMED-CT|123&_format=xml");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertDatabase(String theCriteria) {
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.DATABASE, mySubscriptionStrategyEvaluator.determineStrategy(theCriteria));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertInMemory(String theCriteria) {
|
||||||
|
assertEquals(SubscriptionMatchingStrategy.IN_MEMORY, mySubscriptionStrategyEvaluator.determineStrategy(theCriteria));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -327,6 +327,12 @@
|
||||||
The casing of the base64Binary datatype was incorrect in the DSTU3 and R4 model classes.
|
The casing of the base64Binary datatype was incorrect in the DSTU3 and R4 model classes.
|
||||||
This has been corrected.
|
This has been corrected.
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
Add a "subscription-matching-strategy" meta tag to incoming subscriptions with value of IN_MEMORY
|
||||||
|
or DATABASE indicating whether the subscription can be matched against new resources in-memory or
|
||||||
|
whether a call out to the database may be required. I say "may" because subscription matches fail fast
|
||||||
|
so a negative match may be performed in-memory, but a positive match will require a database call.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.6.0" date="2018-11-12" description="Food">
|
<release version="3.6.0" date="2018-11-12" description="Food">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue