diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 27431bc7fd8..d5118a04182 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -276,6 +276,10 @@ public class Constants {
public static final String RESOURCE_PARTITION_ID = Constants.class.getName() + "_RESOURCE_PARTITION_ID";
public static final String CT_APPLICATION_GZIP = "application/gzip";
public static final String[] EMPTY_STRING_ARRAY = new String[0];
+ public static final String SUBSCRIPTION_MULTITYPE_PREFIX = "[";
+ public static final String SUBSCRIPTION_MULTITYPE_SUFFIX = "]";
+ public static final String SUBSCRIPTION_MULTITYPE_STAR = "*";
+ public static final String SUBSCRIPTION_STAR_CRITERIA = SUBSCRIPTION_MULTITYPE_PREFIX + SUBSCRIPTION_MULTITYPE_STAR + SUBSCRIPTION_MULTITYPE_SUFFIX;
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
index 75eaf91a349..6337301f156 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java
@@ -1402,6 +1402,28 @@ public class FhirTerser {
}
+ /**
+ * Clones a resource object, copying all data elements from theSource into a new copy of the same type.
+ *
+ * Note that:
+ *
+ *
Only FHIR data elements are copied (i.e. user data maps are not copied)
+ *
If a class extending a HAPI FHIR type (e.g. an instance of a class extending the Patient class) is supplied, an instance of the base type will be returned.
+ *
+ *
+ *
+ * @param theSource The source resource
+ * @return A copy of the source resource
+ * @since 5.6.0
+ */
+ @SuppressWarnings("unchecked")
+ public T clone(T theSource) {
+ Validate.notNull(theSource, "theSource must not be null");
+ T target = (T) myContext.getResourceDefinition(theSource).newInstance();
+ cloneInto(theSource, target, false);
+ return target;
+ }
+
public enum OptionsEnum {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3110-allow-multitype-subscription-criteria.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3110-allow-multitype-subscription-criteria.yaml
new file mode 100644
index 00000000000..334302a42ba
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_6_0/3110-allow-multitype-subscription-criteria.yaml
@@ -0,0 +1,6 @@
+---
+type: add
+issue: 3110
+title: "Subscription criteria in the HAPI FHIR JPA server now supports an optional alternate syntax of
+ `[*]` (all resources of all types) and `[resourcename,resourcename,...]` (all resources of the
+ given types. Note that no search parameters may be specitied with this syntax."
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java
index dd8790ead1b..313401c6a26 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java
@@ -53,7 +53,7 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
mySubscriptionDao.update(s);
fail();
} catch (UnprocessableEntityException e) {
- assertEquals("Subscription.criteria must be in the form \"{Resource Type}?[params]\"", e.getMessage());
+ assertEquals("Subscription.criteria contains invalid/unsupported resource type: FOO", e.getMessage());
}
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
index f1825ef9418..8b094564192 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
@@ -1,62 +1,58 @@
package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.channel.impl.LinkedBlockingChannel;
import ca.uhn.fhir.jpa.subscription.submit.interceptor.SubscriptionMatcherInterceptor;
-import ca.uhn.fhir.rest.annotation.Create;
-import ca.uhn.fhir.rest.annotation.ResourceParam;
-import ca.uhn.fhir.rest.annotation.Transaction;
-import ca.uhn.fhir.rest.annotation.TransactionParam;
-import ca.uhn.fhir.rest.annotation.Update;
-import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
-import ca.uhn.fhir.rest.server.IResourceProvider;
-import ca.uhn.fhir.rest.server.RestfulServer;
-import ca.uhn.fhir.test.utilities.JettyUtil;
+import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderExtension;
+import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
+import ca.uhn.fhir.test.utilities.server.TransactionCapturingProviderExtension;
import ca.uhn.fhir.util.BundleUtil;
-import com.google.common.collect.Lists;
import net.ttddyy.dsproxy.QueryCount;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
-import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Organization;
+import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.extension.RegisterExtension;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.RequestParam;
import javax.annotation.PostConstruct;
-import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Enumeration;
import java.util.List;
public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class);
protected static int ourListenerPort;
- protected static List ourContentTypes = Collections.synchronizedList(new ArrayList<>());
- protected static List ourHeaders = Collections.synchronizedList(new ArrayList<>());
- protected static List ourTransactions = Collections.synchronizedList(Lists.newArrayList());
- protected static List ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
- protected static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
- private static Server ourListenerServer;
- private static SingleQueryCountHolder ourCountHolder;
- private static String ourListenerServerBase;
+
+ @Order(0)
+ @RegisterExtension
+ protected static RestfulServerExtension ourRestfulServer = new RestfulServerExtension(FhirContext.forR4Cached());
+ @Order(1)
+ @RegisterExtension
+ protected static HashMapResourceProviderExtension ourPatientProvider = new HashMapResourceProviderExtension<>(ourRestfulServer, Patient.class);
+ @Order(1)
+ @RegisterExtension
+ protected static HashMapResourceProviderExtension ourObservationProvider = new HashMapResourceProviderExtension<>(ourRestfulServer, Observation.class);
+ @Order(1)
+ @RegisterExtension
+ protected static TransactionCapturingProviderExtension ourTransactionProvider = new TransactionCapturingProviderExtension<>(ourRestfulServer, Bundle.class);
+ protected static SingleQueryCountHolder ourCountHolder;
+ @Order(1)
+ @RegisterExtension
+ protected static HashMapResourceProviderExtension ourOrganizationProvider = new HashMapResourceProviderExtension<>(ourRestfulServer, Organization.class);
@Autowired
protected SubscriptionTestUtil mySubscriptionTestUtil;
@Autowired
@@ -92,12 +88,6 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@BeforeEach
public void beforeReset() throws Exception {
- ourCreatedObservations.clear();
- ourUpdatedObservations.clear();
- ourTransactions.clear();
- ourContentTypes.clear();
- ourHeaders.clear();
-
// Delete all Subscriptions
if (myClient != null) {
Bundle allSubscriptions = myClient.search().forResource(Subscription.class).returnBundle(Bundle.class).execute();
@@ -137,7 +127,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
Subscription.SubscriptionChannelComponent channel = subscription.getChannel();
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
channel.setPayload(thePayload);
- channel.setEndpoint(ourListenerServerBase);
+ channel.setEndpoint(ourRestfulServer.getBaseUrl());
return subscription;
}
@@ -169,55 +159,24 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
return observation;
}
+ protected Patient sendPatient() {
+ Patient patient = new Patient();
+ patient.setActive(true);
- public static class ObservationResourceProvider implements IResourceProvider {
-
- @Create
- public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
- ourLog.info("Received Listener Create");
- ourContentTypes.add(theRequest.getHeader(ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
- ourCreatedObservations.add(theObservation);
- extractHeaders(theRequest);
- return new MethodOutcome(new IdType("Observation/1"), true);
- }
-
- private void extractHeaders(HttpServletRequest theRequest) {
- java.util.Enumeration headerNamesEnum = theRequest.getHeaderNames();
- while (headerNamesEnum.hasMoreElements()) {
- String nextName = headerNamesEnum.nextElement();
- Enumeration valueEnum = theRequest.getHeaders(nextName);
- while (valueEnum.hasMoreElements()) {
- String nextValue = valueEnum.nextElement();
- ourHeaders.add(nextName + ": " + nextValue);
- }
- }
- }
-
- @Override
- public Class extends IBaseResource> getResourceType() {
- return Observation.class;
- }
-
- @Update
- public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
- ourLog.info("Received Listener Update");
- ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
- ourUpdatedObservations.add(theObservation);
- extractHeaders(theRequest);
- return new MethodOutcome(new IdType("Observation/1"), false);
- }
+ IIdType id = myPatientDao.create(patient).getId();
+ patient.setId(id);
+ return patient;
}
- public static class PlainProvider {
+ protected Organization sendOrganization() {
+ Organization org = new Organization();
+ org.setName("ORG");
- @Transaction
- public Bundle transaction(@TransactionParam Bundle theInput) {
- ourLog.info("Received transaction update");
- ourTransactions.add(theInput);
- return theInput;
- }
+ IIdType id = myOrganizationDao.create(org).getId();
+ org.setId(id);
+ return org;
}
@AfterAll
@@ -229,31 +188,4 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
return ourCountHolder.getQueryCountMap().get("");
}
- @BeforeAll
- public static void startListenerServer() throws Exception {
- RestfulServer ourListenerRestServer = new RestfulServer(FhirContext.forR4());
-
- ourListenerRestServer.registerProvider(new ObservationResourceProvider());
- ourListenerRestServer.registerProvider(new PlainProvider());
-
- ourListenerServer = new Server(0);
-
- ServletContextHandler proxyHandler = new ServletContextHandler();
- proxyHandler.setContextPath("/");
-
- ServletHolder servletHolder = new ServletHolder();
- servletHolder.setServlet(ourListenerRestServer);
- proxyHandler.addServlet(servletHolder, "/fhir/context/*");
-
- ourListenerServer.setHandler(proxyHandler);
- JettyUtil.startServer(ourListenerServer);
- ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer);
- ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
- }
-
- @AfterAll
- public static void stopListenerServer() throws Exception {
- JettyUtil.closeServer(ourListenerServer);
- }
-
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
index eca510cbe77..c3490ba07b0 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
@@ -11,12 +11,13 @@ import java.util.List;
public class CountingInterceptor implements ChannelInterceptor {
+ private static final Logger ourLog = LoggerFactory.getLogger(CountingInterceptor.class);
private List mySent = new ArrayList<>();
public int getSentCount(String theContainingKeyword) {
- return (int)mySent.stream().filter(t -> t.contains(theContainingKeyword)).count();
+ return (int) mySent.stream().filter(t -> t.contains(theContainingKeyword)).count();
}
-private static final Logger ourLog = LoggerFactory.getLogger(CountingInterceptor.class);
+
@Override
public void afterSendCompletion(Message> theMessage, MessageChannel theChannel, boolean theSent, Exception theException) {
ourLog.info("Counting another instance: {}", theMessage);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java
index 8e829406d72..adea6358cfe 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionValidatingInterceptorTest.java
@@ -83,6 +83,25 @@ public class SubscriptionValidatingInterceptorTest {
}
}
+ @Test
+ public void testValidate_RestHook_MultitypeResourceTypeNotSupported() {
+ when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(false);
+
+ Subscription subscription = new Subscription();
+ subscription.setStatus(Subscription.SubscriptionStatus.ACTIVE);
+ subscription.setCriteria("[Patient]");
+ subscription.getChannel().setType(Subscription.SubscriptionChannelType.RESTHOOK);
+ subscription.getChannel().setPayload("application/fhir+json");
+ subscription.getChannel().setEndpoint("http://foo");
+
+ try {
+ mySvc.validateSubmittedSubscription(subscription);
+ fail();
+ } catch (UnprocessableEntityException e) {
+ assertThat(e.getMessage(), containsString("Subscription.criteria contains invalid/unsupported resource type: Patient"));
+ }
+ }
+
@Test
public void testValidate_RestHook_NoEndpoint() {
when(myDaoRegistry.isResourceTypeSupported(eq("Patient"))).thenReturn(true);
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
index b9b72b9fb8c..073975810d1 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
@@ -8,10 +8,20 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.HapiExtensions;
-import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.BooleanType;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.CodeableConcept;
+import org.hl7.fhir.r4.model.Coding;
+import org.hl7.fhir.r4.model.Enumerations;
+import org.hl7.fhir.r4.model.IdType;
+import org.hl7.fhir.r4.model.Meta;
+import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
+import org.hl7.fhir.r4.model.SearchParameter;
+import org.hl7.fhir.r4.model.StringType;
+import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -68,9 +78,10 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
}
@Test
@@ -92,15 +103,14 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- int idx = 0;
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
- assertEquals("1", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
- assertEquals("1", ourUpdatedObservations.get(idx).getMeta().getVersionId());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals("1", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getMeta().getVersionId());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdentifierFirstRep().getValue());
/*
* Send version 2
@@ -112,15 +122,14 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- idx++;
- waitForSize(0, ourCreatedObservations);
- waitForSize(2, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
- assertEquals("2", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
- assertEquals("2", ourUpdatedObservations.get(idx).getMeta().getVersionId());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals("2", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(2);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getMeta().getVersionId());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getIdentifierFirstRep().getValue());
}
@Test
@@ -151,9 +160,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Send the transaction
mySystemDao.transaction(null, bundle);
- waitForSize(1, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(1);
- assertThat(ourUpdatedObservations.get(0).getSubject().getReference(), matchesPattern("Patient/[0-9]+"));
+ assertThat(ourObservationProvider.getStoredResources().get(0).getSubject().getReference(), matchesPattern("Patient/[0-9]+"));
}
@Test
@@ -183,14 +192,13 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- int idx = 0;
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
- assertEquals("1", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
- assertEquals("1", ourUpdatedObservations.get(idx).getMeta().getVersionId());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals("1", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getMeta().getVersionId());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdentifierFirstRep().getValue());
/*
* Send version 2
@@ -209,14 +217,13 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- idx++;
- waitForSize(0, ourCreatedObservations);
- waitForSize(2, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
- assertEquals("2", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
- assertEquals("2", ourUpdatedObservations.get(idx).getMeta().getVersionId());
- assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
- assertEquals("2", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(2);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getMeta().getVersionId());
+ assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourObservationProvider.getStoredResources().get(0).getMeta().getLastUpdatedElement().getValueAsString());
+ assertEquals("2", ourObservationProvider.getStoredResources().get(0).getIdentifierFirstRep().getValue());
}
@Test
@@ -237,7 +244,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
myObservationDao.create(observation);
}
- waitForSize(100, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(100);
}
@@ -265,7 +272,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
observation.setStatus(Observation.ObservationStatus.FINAL);
myObservationDao.create(observation);
- waitForSize(1, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(1);
}
@Test
@@ -297,9 +304,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
// Send a meta-add
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
@@ -312,8 +319,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
// Send a meta-delete
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
@@ -326,8 +333,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
}
@@ -349,9 +356,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
// Send a meta-add
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
@@ -364,8 +371,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(3, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(3);
// Send a meta-delete
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
@@ -378,8 +385,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(5, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(5);
}
@@ -399,9 +406,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
// Send an update with no changes
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
@@ -410,8 +417,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
}
@@ -432,11 +439,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Observation observation1 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
- IdType idElement = ourUpdatedObservations.get(0).getIdElement();
+ IdType idElement = ourObservationProvider.getStoredResources().get(0).getIdElement();
assertEquals(observation1.getIdElement().getIdPart(), idElement.getIdPart());
// VersionId is present
assertEquals(observation1.getIdElement().getVersionIdPart(), idElement.getVersionIdPart());
@@ -454,11 +461,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(2, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(1));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(2);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(1));
- idElement = ourUpdatedObservations.get(1).getIdElement();
+ idElement = ourObservationProvider.getResourceUpdates().get(1).getIdElement();
assertEquals(observation2.getIdElement().getIdPart(), idElement.getIdPart());
// Now VersionId is stripped
assertEquals(null, idElement.getVersionIdPart());
@@ -495,11 +502,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
- waitForSize(0, ourCreatedObservations);
- waitForSize(2, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(2);
- Observation observation1 = ourUpdatedObservations.get(0);
- Observation observation2 = ourUpdatedObservations.get(1);
+ Observation observation1 = ourObservationProvider.getResourceUpdates().get(0);
+ Observation observation2 = ourObservationProvider.getResourceUpdates().get(1);
assertEquals("1", observation1.getIdElement().getVersionIdPart());
assertNull(observation1.getNoteFirstRep().getText());
@@ -544,11 +551,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
- waitForSize(0, ourCreatedObservations);
- waitForSize(2, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(2);
- Observation observation1 = ourUpdatedObservations.get(0);
- Observation observation2 = ourUpdatedObservations.get(1);
+ Observation observation1 = ourObservationProvider.getResourceUpdates().get(0);
+ Observation observation2 = ourObservationProvider.getResourceUpdates().get(1);
assertEquals("2", observation1.getIdElement().getVersionIdPart());
assertEquals("changed", observation1.getNoteFirstRep().getText());
@@ -572,11 +579,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
- assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
Subscription subscriptionTemp = myClient.read(Subscription.class, subscription2.getId());
assertNotNull(subscriptionTemp);
@@ -589,8 +596,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see two subscription notifications
- waitForSize(0, ourCreatedObservations);
- waitForSize(3, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(3);
myClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
waitForActivatedSubscriptionCount(1);
@@ -599,8 +606,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see only one subscription notification
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3 = myClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
@@ -612,8 +619,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see no subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3a = myClient.read(Observation.class, observationTemp3.getId());
@@ -626,8 +633,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see only one subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(5, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(5);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
@@ -652,11 +659,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
- assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
Subscription subscriptionTemp = myClient.read(Subscription.class, subscription2.getId());
assertNotNull(subscriptionTemp);
@@ -669,8 +676,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see two subscription notifications
- waitForSize(0, ourCreatedObservations);
- waitForSize(3, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(3);
myClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
waitForQueueToDrain();
@@ -679,8 +686,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see only one subscription notification
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3 = myClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
@@ -692,8 +699,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see no subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3a = myClient.read(Observation.class, observationTemp3.getId());
@@ -706,8 +713,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see only one subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(5, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(5);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
@@ -730,9 +737,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_XML_NEW, ourRestfulServer.getRequestContentTypes().get(0));
Subscription subscriptionTemp = myClient.read(Subscription.class, subscription2.getId());
assertNotNull(subscriptionTemp);
@@ -744,8 +751,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see two subscription notifications
- waitForSize(0, ourCreatedObservations);
- waitForSize(3, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(3);
myClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
@@ -753,8 +760,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see only one subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3 = myClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
@@ -766,8 +773,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see no subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(4, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(4);
Observation observation3a = myClient.read(Observation.class, observationTemp3.getId());
@@ -780,14 +787,68 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see only one subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(5, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(5);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
assertFalse(observation2.getId().isEmpty());
}
+ @Test
+ public void testRestHookSubscriptionStarCriteria() throws Exception {
+ String payload = "application/json";
+
+ String code = "1000000050";
+ String criteria1 = "[*]";
+
+ createSubscription(criteria1, payload);
+ waitForActivatedSubscriptionCount(1);
+
+ sendObservation(code, "SNOMED-CT");
+ sendPatient();
+
+ waitForQueueToDrain();
+
+ // Should see 1 subscription notification for each type
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ ourPatientProvider.waitForCreateCount(0);
+ ourPatientProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(1));
+
+ }
+
+
+ @Test
+ public void testRestHookSubscriptionMultiTypeCriteria() throws Exception {
+ String payload = "application/json";
+
+ String code = "1000000050";
+ String criteria1 = "[Observation,Patient]";
+
+ createSubscription(criteria1, payload);
+ waitForActivatedSubscriptionCount(1);
+
+ sendOrganization();
+ sendObservation(code, "SNOMED-CT");
+ sendPatient();
+
+ waitForQueueToDrain();
+
+ // Should see 1 subscription notification for each type
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ ourPatientProvider.waitForCreateCount(0);
+ ourPatientProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(1));
+ ourOrganizationProvider.waitForCreateCount(0);
+ ourOrganizationProvider.waitForUpdateCount(0);
+
+ }
+
@Test
public void testSubscriptionTriggerViaSubscription() throws Exception {
String payload = "application/xml";
@@ -831,11 +892,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
myClient.transaction().withBundle(requestBundle).execute();
// Should see 1 subscription notification
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_XML_NEW, ourRestfulServer.getRequestContentTypes().get(0));
- Observation obs = ourUpdatedObservations.get(0);
+ Observation obs = ourObservationProvider.getStoredResources().get(0);
ourLog.info("Observation content: {}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(obs));
}
@@ -857,7 +918,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Criteria didn't match, shouldn't see any updates
waitForQueueToDrain();
Thread.sleep(1000);
- assertEquals(0, ourUpdatedObservations.size());
+ assertEquals(0, ourObservationProvider.getCountUpdate());
Subscription subscriptionTemp = myClient.read().resource(Subscription.class).withId(subscription2.getId()).execute();
assertNotNull(subscriptionTemp);
@@ -872,8 +933,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see a subscription notification this time
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
myClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
@@ -881,7 +942,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// No more matches
Thread.sleep(1000);
- assertEquals(1, ourUpdatedObservations.size());
+ assertEquals(1, ourObservationProvider.getCountUpdate());
}
@Test
@@ -900,9 +961,9 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_XML_NEW, ourRestfulServer.getRequestContentTypes().get(0));
}
@Test
@@ -940,11 +1001,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
- assertThat(ourHeaders, hasItem("X-Foo: FOO"));
- assertThat(ourHeaders, hasItem("X-Bar: BAR"));
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertThat(ourRestfulServer.getRequestHeaders().get(0), hasItem("X-Foo: FOO"));
+ assertThat(ourRestfulServer.getRequestHeaders().get(0), hasItem("X-Bar: BAR"));
}
@Test
@@ -961,8 +1022,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
// Disable
subscription.setStatus(Subscription.SubscriptionStatus.OFF);
@@ -974,8 +1035,8 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
+ assertEquals(0, ourObservationProvider.getCountCreate());
+ ourObservationProvider.waitForUpdateCount(1);
}
@@ -1096,7 +1157,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
MethodOutcome methodOutcome = myClient.create().resource(bodySite).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
- waitForSize(1, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(1);
}
{
Observation observation = new Observation();
@@ -1104,14 +1165,14 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
MethodOutcome methodOutcome = myClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
- waitForSize(2, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(2);
}
{
Observation observation = new Observation();
MethodOutcome methodOutcome = myClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
- waitForSize(2, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(2);
}
{
Observation observation = new Observation();
@@ -1119,7 +1180,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
MethodOutcome methodOutcome = myClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
- waitForSize(2, ourUpdatedObservations);
+ ourObservationProvider.waitForUpdateCount(2);
}
}
@@ -1148,14 +1209,11 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
- waitForSize(1, ourTransactions);
- ourLog.info("Received transaction: {}", myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ourTransactions.get(0)));
+ ourTransactionProvider.waitForTransactionCount(1);
- Bundle xact = ourTransactions.get(0);
+ Bundle xact = ourTransactionProvider.getTransactions().get(0);
assertEquals(2, xact.getEntry().size());
-
- ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ourTransactions.get(0)));
}
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java
index 4e2f76b3dd7..2cb229b4ac8 100755
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithInterceptorR4Test.java
@@ -103,10 +103,11 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
sendObservation();
deliveryLatch.await(10, TimeUnit.SECONDS);
- assertEquals(0, ourCreatedObservations.size());
- assertEquals(1, ourUpdatedObservations.size());
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
- assertEquals("Observation/A", ourUpdatedObservations.get(0).getId());
+
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
+ assertEquals("Observation/A/_history/1", ourObservationProvider.getStoredResources().get(0).getId());
assertTrue(ourHitBeforeRestHookDelivery);
assertTrue(ourHitAfterRestHookDelivery);
}
@@ -125,12 +126,12 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
sendObservation();
deliveryLatch.await(10, TimeUnit.SECONDS);
- assertEquals(0, ourCreatedObservations.size());
- assertEquals(1, ourUpdatedObservations.size());
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
assertTrue(ourHitBeforeRestHookDelivery);
assertTrue(ourHitAfterRestHookDelivery);
- assertThat(ourHeaders, hasItem("X-Foo: Bar"));
+ assertThat(ourRestfulServer.getRequestHeaders().get(0), hasItem("X-Foo: Bar"));
}
@Test
@@ -174,7 +175,7 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
sendObservation();
Thread.sleep(1000);
- assertEquals(0, ourUpdatedObservations.size());
+ ourObservationProvider.waitForUpdateCount(0);
}
@Test
@@ -242,11 +243,11 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
// Should see 1 subscription notification
waitForQueueToDrain();
- waitForSize(0, ourCreatedObservations);
- waitForSize(1, ourUpdatedObservations);
- assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(1);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourRestfulServer.getRequestContentTypes().get(0));
- assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
+ assertEquals("1", ourObservationProvider.getStoredResources().get(0).getIdElement().getVersionIdPart());
Subscription subscriptionTemp = myClient.read(Subscription.class, subscription2.getId());
assertNotNull(subscriptionTemp);
@@ -259,8 +260,8 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
// Should see two subscription notifications
- waitForSize(0, ourCreatedObservations);
- waitForSize(3, ourUpdatedObservations);
+ ourObservationProvider.waitForCreateCount(0);
+ ourObservationProvider.waitForUpdateCount(3);
ourLog.info("Messages:\n " + messages.stream().collect(Collectors.joining("\n ")));
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index 230d1368935..8e7602383f1 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -54,7 +54,6 @@ import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.text.StringTokenizer;
-import org.apache.commons.text.matcher.StringMatcher;
import org.fhir.ucum.Pair;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
@@ -1556,8 +1555,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Nonnull
public static String[] splitPathsR4(@Nonnull String thePaths) {
StringTokenizer tok = new StringTokenizer(thePaths, " |");
- StringMatcher trimmerMatcher = (buffer, start, bufferStart, bufferEnd) -> (buffer[start] <= 32) ? 1 : 0;
- tok.setTrimmerMatcher(trimmerMatcher);
+ tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
return tok.getTokenArray();
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/StringTrimmingTrimmerMatcher.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/StringTrimmingTrimmerMatcher.java
new file mode 100644
index 00000000000..fc421540cae
--- /dev/null
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/StringTrimmingTrimmerMatcher.java
@@ -0,0 +1,15 @@
+package ca.uhn.fhir.jpa.searchparam.extractor;
+
+import org.apache.commons.text.matcher.StringMatcher;
+
+/**
+ * Utility class that works with the commons-text
+ * {@link org.apache.commons.text.StringTokenizer}
+ * class to return tokens that are whitespace trimmed.
+ */
+public class StringTrimmingTrimmerMatcher implements StringMatcher {
+ @Override
+ public int isMatch(char[] buffer, int start, int bufferStart, int bufferEnd) {
+ return (buffer[start] <= 32) ? 1 : 0;
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java
index 2d5947d8f0c..4673e3407ed 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/matching/SubscriptionStrategyEvaluator.java
@@ -22,6 +22,8 @@ package ca.uhn.fhir.jpa.subscription.match.matcher.matching;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult;
import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryResourceMatcher;
+import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionCriteriaParser;
+import ca.uhn.fhir.rest.api.Constants;
import org.springframework.beans.factory.annotation.Autowired;
public class SubscriptionStrategyEvaluator {
@@ -37,9 +39,16 @@ public class SubscriptionStrategyEvaluator {
}
public SubscriptionMatchingStrategy determineStrategy(String theCriteria) {
- InMemoryMatchResult result = myInMemoryResourceMatcher.canBeEvaluatedInMemory(theCriteria);
- if (result.supported()) {
- return SubscriptionMatchingStrategy.IN_MEMORY;
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = SubscriptionCriteriaParser.parse(theCriteria);
+ if (criteria != null) {
+ if (criteria.getCriteria() != null) {
+ InMemoryMatchResult result = myInMemoryResourceMatcher.canBeEvaluatedInMemory(theCriteria);
+ if (result.supported()) {
+ return SubscriptionMatchingStrategy.IN_MEMORY;
+ }
+ } else {
+ return SubscriptionMatchingStrategy.IN_MEMORY;
+ }
}
return SubscriptionMatchingStrategy.DATABASE;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParser.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParser.java
new file mode 100644
index 00000000000..0923e2e8cbf
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParser.java
@@ -0,0 +1,117 @@
+package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber;
+
+import ca.uhn.fhir.jpa.searchparam.extractor.StringTrimmingTrimmerMatcher;
+import ca.uhn.fhir.rest.api.Constants;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.commons.text.StringTokenizer;
+
+import javax.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.trim;
+
+public enum SubscriptionCriteriaParser {
+ ;
+
+ public enum TypeEnum {
+
+ /**
+ * Normal search URL expression
+ */
+ SEARCH_EXPRESSION,
+
+ /**
+ * Collection of resource types
+ */
+ MULTITYPE_EXPRESSION,
+
+ /**
+ * All types
+ */
+ STARTYPE_EXPRESSION
+
+ }
+
+ public static class SubscriptionCriteria {
+
+ private final TypeEnum myType;
+ private final String myCriteria;
+ private final Set myApplicableResourceTypes;
+
+ private SubscriptionCriteria(TypeEnum theType, String theCriteria, Set theApplicableResourceTypes) {
+ myType = theType;
+ myCriteria = theCriteria;
+ myApplicableResourceTypes = theApplicableResourceTypes;
+ }
+
+ @Override
+ public String toString() {
+ ToStringBuilder retVal = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ retVal.append("type", myType);
+ if (isNotBlank(myCriteria)) {
+ retVal.append("criteria", myCriteria);
+ }
+ if (myApplicableResourceTypes != null) {
+ retVal.append("applicableResourceTypes", myApplicableResourceTypes);
+ }
+ return retVal.toString();
+ }
+
+ public TypeEnum getType() {
+ return myType;
+ }
+
+ public String getCriteria() {
+ return myCriteria;
+ }
+
+ public Set getApplicableResourceTypes() {
+ return myApplicableResourceTypes;
+ }
+ }
+
+ @Nullable
+ public static SubscriptionCriteria parse(String theCriteria) {
+ String criteria = trim(theCriteria);
+ if (isBlank(criteria)) {
+ return null;
+ }
+
+ if (criteria.startsWith(Constants.SUBSCRIPTION_MULTITYPE_PREFIX)) {
+ if (criteria.endsWith(Constants.SUBSCRIPTION_MULTITYPE_SUFFIX)) {
+ String multitypeExpression = criteria.substring(1, criteria.length() - 1);
+ StringTokenizer tok = new StringTokenizer(multitypeExpression, ",");
+ tok.setTrimmerMatcher(new StringTrimmingTrimmerMatcher());
+ List types = tok.getTokenList();
+ if (types.isEmpty()) {
+ return null;
+ }
+ if (types.contains(Constants.SUBSCRIPTION_MULTITYPE_STAR)) {
+ return new SubscriptionCriteria(TypeEnum.STARTYPE_EXPRESSION, null, null);
+ }
+ Set typesSet = Sets.newHashSet(types);
+ return new SubscriptionCriteria(TypeEnum.MULTITYPE_EXPRESSION, null, typesSet);
+ }
+ }
+
+ if (Character.isLetter(criteria.charAt(0))) {
+ String criteriaType = criteria;
+ int questionMarkIdx = criteriaType.indexOf('?');
+ if (questionMarkIdx > 0) {
+ criteriaType = criteriaType.substring(0, questionMarkIdx);
+ }
+ Set types = Collections.singleton(criteriaType);
+ return new SubscriptionCriteria(TypeEnum.SEARCH_EXPRESSION, criteria, types);
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java
index ee5ffe12768..c49a14ef8a9 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionMatchingSubscriber.java
@@ -15,7 +15,6 @@ import ca.uhn.fhir.jpa.subscription.model.ResourceDeliveryMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedJsonMessage;
import ca.uhn.fhir.jpa.subscription.model.ResourceModifiedMessage;
import ca.uhn.fhir.rest.api.EncodingEnum;
-import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
@@ -136,18 +135,24 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
}
}
- if (!validCriteria(nextActiveSubscription, resourceId)) {
+ if (!resourceTypeIsAppropriateForSubscription(nextActiveSubscription, resourceId)) {
continue;
}
- InMemoryMatchResult matchResult = mySubscriptionMatcher.match(nextActiveSubscription.getSubscription(), theMsg);
- if (!matchResult.matched()) {
- continue;
+ InMemoryMatchResult matchResult;
+ if (nextActiveSubscription.getCriteria().getType() == SubscriptionCriteriaParser.TypeEnum.SEARCH_EXPRESSION) {
+ matchResult = mySubscriptionMatcher.match(nextActiveSubscription.getSubscription(), theMsg);
+ if (!matchResult.matched()) {
+ continue;
+ }
+ ourLog.debug("Subscription {} was matched by resource {} {}",
+ nextActiveSubscription.getId(),
+ resourceId.toUnqualifiedVersionless().getValue(),
+ matchResult.isInMemory() ? "in-memory" : "by querying the repository");
+ } else {
+ matchResult = InMemoryMatchResult.successfulMatch();
+ matchResult.setInMemory(true);
}
- ourLog.debug("Subscription {} was matched by resource {} {}",
- nextActiveSubscription.getId(),
- resourceId.toUnqualifiedVersionless().getValue(),
- matchResult.isInMemory() ? "in-memory" : "by querying the repository");
IBaseResource payload = theMsg.getNewPayload(myFhirContext);
CanonicalSubscription subscription = nextActiveSubscription.getSubscription();
@@ -215,28 +220,26 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
return theActiveSubscription.getId();
}
- private boolean validCriteria(ActiveSubscription theActiveSubscription, IIdType theResourceId) {
- String criteriaString = theActiveSubscription.getCriteriaString();
+ private boolean resourceTypeIsAppropriateForSubscription(ActiveSubscription theActiveSubscription, IIdType theResourceId) {
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = theActiveSubscription.getCriteria();
String subscriptionId = getId(theActiveSubscription);
String resourceType = theResourceId.getResourceType();
- if (StringUtils.isBlank(criteriaString)) {
- return false;
- }
-
// see if the criteria matches the created object
- ourLog.trace("Checking subscription {} for {} with criteria {}", subscriptionId, resourceType, criteriaString);
- String criteriaResource = criteriaString;
- int index = criteriaResource.indexOf("?");
- if (index != -1) {
- criteriaResource = criteriaResource.substring(0, criteriaResource.indexOf("?"));
- }
+ ourLog.trace("Checking subscription {} for {} with criteria {}", subscriptionId, resourceType, criteria);
- if (resourceType != null && !criteriaResource.equals(resourceType)) {
- ourLog.trace("Skipping subscription search for {} because it does not match the criteria {}", resourceType, criteriaString);
+ if (criteria == null) {
return false;
}
- return true;
+ switch (criteria.getType()) {
+ default:
+ case SEARCH_EXPRESSION:
+ case MULTITYPE_EXPRESSION:
+ return criteria.getApplicableResourceTypes().contains(resourceType);
+ case STARTYPE_EXPRESSION:
+ return !resourceType.equals("Subscription");
+ }
+
}
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java
index 12a854f229e..8071a01366a 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/match/registry/ActiveSubscription.java
@@ -20,41 +20,41 @@ package ca.uhn.fhir.jpa.subscription.match.registry;
* #L%
*/
+import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionCriteriaParser;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
public class ActiveSubscription {
- private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
- private CanonicalSubscription mySubscription;
+ private SubscriptionCriteriaParser.SubscriptionCriteria myCriteria;
private final String myChannelName;
private final String myId;
+ private CanonicalSubscription mySubscription;
private boolean flagForDeletion;
public ActiveSubscription(CanonicalSubscription theSubscription, String theChannelName) {
- mySubscription = theSubscription;
myChannelName = theChannelName;
myId = theSubscription.getIdPart();
+ setSubscription(theSubscription);
+ }
+
+ public SubscriptionCriteriaParser.SubscriptionCriteria getCriteria() {
+ return myCriteria;
}
public CanonicalSubscription getSubscription() {
return mySubscription;
}
+ public final void setSubscription(CanonicalSubscription theSubscription) {
+ mySubscription = theSubscription;
+ myCriteria = SubscriptionCriteriaParser.parse(theSubscription.getCriteriaString());
+ }
+
public String getChannelName() {
return myChannelName;
}
- public String getCriteriaString() {
- return mySubscription.getCriteriaString();
- }
-
- public void setSubscription(CanonicalSubscription theCanonicalizedSubscription) {
- mySubscription = theCanonicalizedSubscription;
- }
-
public boolean isFlagForDeletion() {
return flagForDeletion;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java
index 78b030cb9e9..8bb9b2551bd 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java
@@ -25,9 +25,9 @@ import ca.uhn.fhir.interceptor.api.Hook;
import ca.uhn.fhir.interceptor.api.Interceptor;
import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
-import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionMatchingStrategy;
import ca.uhn.fhir.jpa.subscription.match.matcher.matching.SubscriptionStrategyEvaluator;
+import ca.uhn.fhir.jpa.subscription.match.matcher.subscriber.SubscriptionCriteriaParser;
import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionCanonicalizer;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType;
@@ -128,6 +128,25 @@ public class SubscriptionValidatingInterceptor {
throw new UnprocessableEntityException(theFieldName + " must be populated");
}
+ SubscriptionCriteriaParser.SubscriptionCriteria parsedCriteria = SubscriptionCriteriaParser.parse(theQuery);
+ if (parsedCriteria == null) {
+ throw new UnprocessableEntityException(theFieldName + " can not be parsed");
+ }
+
+ if (parsedCriteria.getType() == SubscriptionCriteriaParser.TypeEnum.STARTYPE_EXPRESSION) {
+ return;
+ }
+
+ for (String next : parsedCriteria.getApplicableResourceTypes()) {
+ if (!myDaoRegistry.isResourceTypeSupported(next)) {
+ throw new UnprocessableEntityException(theFieldName + " contains invalid/unsupported resource type: " + next);
+ }
+ }
+
+ if (parsedCriteria.getType() != SubscriptionCriteriaParser.TypeEnum.SEARCH_EXPRESSION) {
+ return;
+ }
+
int sep = theQuery.indexOf('?');
if (sep <= 1) {
throw new UnprocessableEntityException(theFieldName + " must be in the form \"{Resource Type}?[params]\"");
@@ -138,9 +157,6 @@ public class SubscriptionValidatingInterceptor {
throw new UnprocessableEntityException(theFieldName + " must be in the form \"{Resource Type}?[params]\"");
}
- if (!myDaoRegistry.isResourceTypeSupported(resType)) {
- throw new UnprocessableEntityException(theFieldName + " contains invalid/unsupported resource type: " + resType);
- }
}
public void validateMessageSubscriptionEndpoint(String theEndpointUrl) {
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParserTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParserTest.java
new file mode 100644
index 00000000000..5215bfad19f
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/match/matcher/subscriber/SubscriptionCriteriaParserTest.java
@@ -0,0 +1,60 @@
+package ca.uhn.fhir.jpa.subscription.match.matcher.subscriber;
+
+import org.junit.jupiter.api.Test;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class SubscriptionCriteriaParserTest {
+
+ @Test
+ public void testSearchExpression() {
+ String expression = "Patient?foo=bar";
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = SubscriptionCriteriaParser.parse(expression);
+ assertEquals(SubscriptionCriteriaParser.TypeEnum.SEARCH_EXPRESSION, criteria.getType());
+ assertEquals(expression, criteria.getCriteria());
+ assertThat(criteria.getApplicableResourceTypes(), containsInAnyOrder("Patient"));
+ assertEquals("SubscriptionCriteriaParser.SubscriptionCriteria[type=SEARCH_EXPRESSION,criteria=Patient?foo=bar,applicableResourceTypes=[Patient]]", criteria.toString());
+ }
+
+ @Test
+ public void testTypeExpression() {
+ String expression = "Patient";
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = SubscriptionCriteriaParser.parse(expression);
+ assertEquals(SubscriptionCriteriaParser.TypeEnum.SEARCH_EXPRESSION, criteria.getType());
+ assertEquals(expression, criteria.getCriteria());
+ assertThat(criteria.getApplicableResourceTypes(), containsInAnyOrder("Patient"));
+ assertEquals("SubscriptionCriteriaParser.SubscriptionCriteria[type=SEARCH_EXPRESSION,criteria=Patient,applicableResourceTypes=[Patient]]", criteria.toString());
+ }
+
+ @Test
+ public void testStarExpression() {
+ String expression = "[*]";
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = SubscriptionCriteriaParser.parse(expression);
+ assertEquals(SubscriptionCriteriaParser.TypeEnum.STARTYPE_EXPRESSION, criteria.getType());
+ assertEquals(null, criteria.getCriteria());
+ assertEquals(null, criteria.getApplicableResourceTypes());
+ assertEquals("SubscriptionCriteriaParser.SubscriptionCriteria[type=STARTYPE_EXPRESSION]", criteria.toString());
+ }
+
+ @Test
+ public void testMultitypeExpression() {
+ String expression = "[Patient , Observation]";
+ SubscriptionCriteriaParser.SubscriptionCriteria criteria = SubscriptionCriteriaParser.parse(expression);
+ assertEquals(SubscriptionCriteriaParser.TypeEnum.MULTITYPE_EXPRESSION, criteria.getType());
+ assertEquals(null, criteria.getCriteria());
+ assertThat(criteria.getApplicableResourceTypes(), containsInAnyOrder("Patient", "Observation"));
+ assertEquals("SubscriptionCriteriaParser.SubscriptionCriteria[type=MULTITYPE_EXPRESSION,applicableResourceTypes=[Observation, Patient]]", criteria.toString());
+ }
+
+ @Test
+ public void testInvalidExpression() {
+ assertNull(SubscriptionCriteriaParser.parse("[]"));
+ assertNull(SubscriptionCriteriaParser.parse(""));
+ assertNull(SubscriptionCriteriaParser.parse(null));
+ assertNull(SubscriptionCriteriaParser.parse(" "));
+ assertNull(SubscriptionCriteriaParser.parse("#123"));
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java
index 746a6af85df..ecedb13f8f1 100644
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistryTest.java
@@ -14,14 +14,14 @@ public class SubscriptionRegistryTest extends BaseSubscriptionRegistryTest {
mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(subscription);
assertRegistrySize(1);
ActiveSubscription origActiveSubscription = mySubscriptionRegistry.get(SUBSCRIPTION_ID);
- assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteriaString());
+ assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteria().getCriteria());
subscription.setCriteria(NEW_CRITERIA);
- assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteriaString());
+ assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteria().getCriteria());
mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(subscription);
assertRegistrySize(1);
ActiveSubscription newActiveSubscription = mySubscriptionRegistry.get(SUBSCRIPTION_ID);
- assertEquals(NEW_CRITERIA, newActiveSubscription.getCriteriaString());
+ assertEquals(NEW_CRITERIA, newActiveSubscription.getCriteria().getCriteria());
// The same object
assertTrue(newActiveSubscription == origActiveSubscription);
}
@@ -34,11 +34,11 @@ public class SubscriptionRegistryTest extends BaseSubscriptionRegistryTest {
assertRegistrySize(1);
ActiveSubscription origActiveSubscription = mySubscriptionRegistry.get(SUBSCRIPTION_ID);
- assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteriaString());
+ assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteria().getCriteria());
setChannel(subscription, Subscription.SubscriptionChannelType.EMAIL);
- assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteriaString());
+ assertEquals(ORIG_CRITERIA, origActiveSubscription.getCriteria().getCriteria());
mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(subscription);
assertRegistrySize(1);
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriberTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriberTest.java
index c8ee403aab2..e72161fa30e 100644
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriberTest.java
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionMatchingSubscriberTest.java
@@ -77,4 +77,30 @@ public class SubscriptionMatchingSubscriberTest extends BaseBlockingQueueSubscri
assertEquals(0, ourContentTypes.size());
}
+
+
+ @Test
+ public void testCriteriaStarOnly() throws InterruptedException {
+ String payload = "application/fhir+xml";
+
+ String code = "1000000050";
+ String criteria1 = "[*]";
+ String criteria2 = "[*]";
+ String criteria3 = "Observation?code=FOO"; // won't match
+
+ sendSubscription(criteria1, payload, ourListenerServerBase);
+ sendSubscription(criteria2, payload, ourListenerServerBase);
+ sendSubscription(criteria3, payload, ourListenerServerBase);
+
+ assertEquals(3, mySubscriptionRegistry.size());
+
+ ourObservationListener.setExpectedCount(2);
+ sendObservation(code, "SNOMED-CT");
+ ourObservationListener.awaitExpected();
+
+ assertEquals(2, ourContentTypes.size());
+ assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
+ }
+
+
}
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderExtension.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderExtension.java
index 0a0c0eda438..d99dcb91f14 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderExtension.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/HashMapResourceProviderExtension.java
@@ -20,15 +20,28 @@ package ca.uhn.fhir.test.utilities.server;
* #L%
*/
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.provider.HashMapResourceProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.awaitility.Awaitility.await;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+
public class HashMapResourceProviderExtension extends HashMapResourceProvider implements BeforeEachCallback, AfterEachCallback {
private final RestfulServerExtension myRestfulServerExtension;
+ private boolean myClearBetweenTests = true;
+ private final List myUpdates = new ArrayList<>();
/**
* Constructor
@@ -46,9 +59,47 @@ public class HashMapResourceProviderExtension extends H
myRestfulServerExtension.getRestfulServer().unregisterProvider(HashMapResourceProviderExtension.this);
}
+ @Override
+ public synchronized MethodOutcome update(T theResource, String theConditional, RequestDetails theRequestDetails) {
+ T resourceClone = getFhirContext().newTerser().clone(theResource);
+ myUpdates.add(resourceClone);
+ return super.update(theResource, theConditional, theRequestDetails);
+ }
+
+ @Override
+ public synchronized void clear() {
+ super.clear();
+ if (myUpdates != null) {
+ myUpdates.clear();
+ }
+ }
+
@Override
public void beforeEach(ExtensionContext context) throws Exception {
- clear();
+ if (myClearBetweenTests) {
+ clear();
+ clearCounts();
+ }
myRestfulServerExtension.getRestfulServer().registerProvider(HashMapResourceProviderExtension.this);
}
+
+ public HashMapResourceProviderExtension dontClearBetweenTests() {
+ myClearBetweenTests = false;
+ return this;
+ }
+
+
+ public void waitForUpdateCount(long theCount) {
+ assertThat(theCount, greaterThanOrEqualTo(getCountUpdate()));
+ await().until(()->getCountUpdate(), equalTo(theCount));
+ }
+
+ public void waitForCreateCount(long theCount) {
+ assertThat(theCount, greaterThanOrEqualTo(getCountCreate()));
+ await().until(()->getCountCreate(), equalTo(theCount));
+ }
+
+ public List getResourceUpdates() {
+ return Collections.unmodifiableList(myUpdates);
+ }
}
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
index 4a8b02f9131..dd6e22bdfdd 100644
--- a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/server/RestfulServerExtension.java
@@ -22,6 +22,10 @@ package ca.uhn.fhir.test.utilities.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Interceptor;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.server.RestfulServer;
@@ -33,23 +37,28 @@ import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
+import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-public class RestfulServerExtension implements BeforeEachCallback, AfterEachCallback {
- private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerExtension.class);
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+public class RestfulServerExtension implements BeforeEachCallback, AfterEachCallback, AfterAllCallback {
+ private static final Logger ourLog = LoggerFactory.getLogger(RestfulServerExtension.class);
+ private final List> myRequestHeaders = new ArrayList<>();
+ private final List myRequestContentTypes = new ArrayList<>();
private FhirContext myFhirContext;
private List