diff --git a/.travis.yml b/.travis.yml
index 911244c5d2f..eb54f367cd1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,7 @@ dist: trusty
language: java
jdk:
- - oraclejdk11
+ - openjdk11
env:
global:
- MAVEN_OPTS="-Xmx10244M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC"
diff --git a/example-projects/README.md b/example-projects/README.md
new file mode 100644
index 00000000000..5ef27babc43
--- /dev/null
+++ b/example-projects/README.md
@@ -0,0 +1,11 @@
+# Unsupported
+
+Most of the projects in this module are no longer supported.
+
+The test in hapi-fhir-jpaserver-cds-example is @Ignored until Chris Schuler is able to make a change to the pom
+this module depends on.
+
+## Supported JPA Example:
+
+The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter)
+project within the [hapifhir](https://github.com/hapifhir) GitHub Organization.
diff --git a/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java b/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java
index 478accecb5b..c1fc5856894 100644
--- a/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java
+++ b/example-projects/hapi-fhir-jpaserver-cds-example/src/test/java/ca/uhn/fhir/jpa/cds/example/CdsExampleTests.java
@@ -10,11 +10,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.Ignore;
+import org.junit.*;
import java.io.*;
import java.net.HttpURLConnection;
@@ -26,7 +22,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Scanner;
-// FIXME KHS
+// TODO Remove @Ignore once Chris Schuler has fixed the external jar this project depends on
@Ignore
public class CdsExampleTests {
private static IGenericClient ourClient;
diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
index 9dfa49e7a21..b6c86dc655b 100644
--- a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
+++ b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
@@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo;
-import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.ServletException;
-
-import org.hl7.fhir.dstu3.model.Bundle;
-import org.hl7.fhir.dstu3.model.Meta;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.WebApplicationContext;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
@@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Meta;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.ServletException;
+import java.util.List;
public class JpaServerDemo extends RestfulServer {
@@ -129,12 +126,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
+ * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
*/
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
/*
* If you are hosting this server at a specific DNS name, the server will try to
diff --git a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
index cf6bb8572ff..3c802e6cb10 100644
--- a/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
+++ b/example-projects/hapi-fhir-jpaserver-dynamic/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
@@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo;
-import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.ServletException;
-
-import org.hl7.fhir.dstu3.model.Bundle;
-import org.hl7.fhir.dstu3.model.Meta;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.WebApplicationContext;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
@@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Meta;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.ServletException;
+import java.util.List;
public class JpaServerDemoDstu2 extends RestfulServer {
@@ -129,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
+ * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
*/
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
/*
* If you are hosting this server at a specific DNS name, the server will try to
diff --git a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
index f45045cf755..93ae03951b4 100644
--- a/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
+++ b/example-projects/hapi-fhir-jpaserver-example-postgres/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
@@ -1,14 +1,5 @@
package ca.uhn.fhir.jpa.demo;
-import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.ServletException;
-
-import org.hl7.fhir.dstu3.model.Meta;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.WebApplicationContext;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
@@ -16,12 +7,18 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
-import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.api.EncodingEnum;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import org.hl7.fhir.dstu3.model.Meta;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.ServletException;
+import java.util.List;
public class JpaServerDemo extends RestfulServer {
@@ -96,12 +93,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
+ * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
*/
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
/*
* If you are hosting this server at a specific DNS name, the server will try to
diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml
index 054dde91604..84ec5a98714 100644
--- a/hapi-deployable-pom/pom.xml
+++ b/hapi-deployable-pom/pom.xml
@@ -99,6 +99,10 @@
javax.mailjavax.mail-api
+
+ javax.activation
+ javax.activation-api
+ com.helgerph-schematron
diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index bd368198eff..04014775ce1 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -48,6 +48,12 @@
com.helgerph-schematrontrue
+
+
+ org.glassfish.jaxb
+ jaxb-core
+
+ com.helger
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java
index b370ea014e1..8f2b8158781 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java
@@ -21,6 +21,7 @@ package ca.uhn.fhir.fluentpath;
*/
import java.util.List;
+import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase;
@@ -36,6 +37,15 @@ public interface IFluentPath {
*/
List evaluate(IBase theInput, String thePath, Class theReturnType);
-
+ /**
+ * Apply the given FluentPath expression against the given input and return
+ * the first match (if any)
+ *
+ * @param theInput The input object (generally a resource or datatype)
+ * @param thePath The fluent path expression
+ * @param theReturnType The type to return (in order to avoid casting)
+ */
+ Optional evaluateFirst(IBase theInput, String thePath, Class theReturnType);
+
}
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 ec8f590d723..1e2330f5424 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
@@ -53,8 +53,8 @@ public class FhirTerser {
if (theChildDefinition == null)
return null;
if (theCurrentList == null || theCurrentList.isEmpty())
- return new ArrayList(Arrays.asList(theChildDefinition.getElementName()));
- List newList = new ArrayList(theCurrentList);
+ return new ArrayList<>(Arrays.asList(theChildDefinition.getElementName()));
+ List newList = new ArrayList<>(theCurrentList);
newList.add(theChildDefinition.getElementName());
return newList;
}
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
index 1ffeda550f2..92f20af6b75 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
@@ -218,13 +218,17 @@
javax.xml.bindjaxb-api
+
+
+
+
+
+
+
+
- com.sun.xml.bind
- jaxb-core
-
-
- com.sun.xml.bind
- jaxb-impl
+ org.glassfish.jaxb
+ jaxb-runtime
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
index 66fac50c071..a77041fd8d3 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
@@ -256,27 +256,30 @@ public abstract class BaseApp {
System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff());
logCommandUsageNoHeader(command);
runCleanupHookAndUnregister();
- System.exit(1);
+ exitDueToException(e);
} catch (CommandFailureException e) {
ourLog.error(e.getMessage());
runCleanupHookAndUnregister();
- if ("true".equals(System.getProperty("test"))) {
- throw e;
- } else {
- System.exit(1);
- }
+ exitDueToException(e);
} catch (Throwable t) {
ourLog.error("Error during execution: ", t);
runCleanupHookAndUnregister();
- if ("true".equals(System.getProperty("test"))) {
- throw new CommandFailureException("Error: " + t.toString(), t);
- } else {
- System.exit(1);
- }
+ exitDueToException(new CommandFailureException("Error: " + t.toString(), t));
}
}
+ private void exitDueToException(Throwable e) {
+ if ("true".equals(System.getProperty("test"))) {
+ if (e instanceof CommandFailureException) {
+ throw (CommandFailureException)e;
+ }
+ throw new Error(e);
+ } else {
+ System.exit(1);
+ }
+ }
+
private void runCleanupHookAndUnregister() {
if (myShutdownHookHasNotRun) {
Runtime.getRuntime().removeShutdownHook(myShutdownHook);
diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
index b4972e3e99d..c2bff0b1cfd 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
+++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
@@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@@ -21,13 +22,11 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletException;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
public class JpaServerDemo extends RestfulServer {
@@ -143,19 +142,14 @@ public class JpaServerDemo extends RestfulServer {
CorsInterceptor corsInterceptor = new CorsInterceptor();
registerInterceptor(corsInterceptor);
- /*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
- */
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
-
DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class);
daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs());
daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity());
daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity());
daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis());
+
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
}
}
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 6f662773851..d0662b73e4a 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -231,14 +231,14 @@
javax.xml.bindjaxb-api
-
+
@@ -360,18 +360,10 @@
xml-apisxml-apis
-
- org.jboss.spec.javax.transaction
- jboss-transaction-api_1.2_spec
-
-
- javax.activation
- activation
-
-
- javax.activation
- javax.activation-api
-
+
+
+
+
@@ -409,9 +401,14 @@
- javax.transaction
- javax.transaction-api
+ com.sun.activation
+ javax.activation
+ 1.2.0
+
+
+
+
javax.mailjavax.mail-api
@@ -428,10 +425,10 @@
-
- com.sun.activation
- javax.activation
-
+
+
+
+
- javax.xml.bind
- jaxb-api
- ${jaxb_api_version}
-
-
- com.sun.xml.bind
- jaxb-core
- ${jaxb_core_version}
-
-
- com.sun.xml.bind
- jaxb-impl
- ${jaxb_core_version}
+ org.glassfish.jaxb
+ jaxb-runtime
+ ${jaxb_runtime_version}
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 18e151eb601..40141da5113 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -2,18 +2,18 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.HapiLocalizer;
-import ca.uhn.fhir.jpa.dao.DatabaseSearchParamProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
-import ca.uhn.fhir.jpa.subscription.config.BaseSubscriptionConfig;
-import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
-import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
+import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher;
+import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
+import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory;
+import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
+import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
@@ -59,7 +59,8 @@ import javax.annotation.Nonnull;
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class),
- @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class)})
+ @ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class),
+ @ComponentScan.Filter(type=FilterType.REGEX, pattern="ca.uhn.fhir.jpa.subscription.module.standalone.*")})
public abstract class BaseConfig implements SchedulingConfigurer {
@@ -132,34 +133,29 @@ public abstract class BaseConfig implements SchedulingConfigurer {
}
@Bean
- protected ISearchParamProvider searchParamProvider() {
- return new DatabaseSearchParamProvider();
+ public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() {
+ return new InMemorySubscriptionMatcher();
+ }
+
+ @Bean
+ public DaoSubscriptionMatcher daoSubscriptionMatcher() {
+ return new DaoSubscriptionMatcher();
}
/**
- * Note: If you're going to use this, you need to provide a bean
- * of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
- * in your own Spring config
+ * Create a @Primary @Bean if you need a different implementation
*/
@Bean
- @Lazy
- public SubscriptionEmailInterceptor subscriptionEmailInterceptor() {
- return new SubscriptionEmailInterceptor();
+ public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() {
+ return new BlockingQueueSubscriptionChannelFactory();
}
@Bean
- @Lazy
- public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
- return new SubscriptionRestHookInterceptor();
+ @Primary
+ public ISubscriptionMatcher subscriptionMatcherCompositeInMemoryDatabase() {
+ return new CompositeInMemoryDaoSubscriptionMatcher(daoSubscriptionMatcher(), inMemorySubscriptionMatcher());
}
- @Bean
- @Lazy
- public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
- return new SubscriptionWebsocketInterceptor();
- }
-
-
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
index f0ca4a9eb7d..88c71f6b89e 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseDstu2Config.java
@@ -116,7 +116,7 @@ public class BaseDstu2Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
- return new SearchParamRegistryDstu2(searchParamProvider());
+ return new SearchParamRegistryDstu2();
}
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java
index 383b52ea84a..c7b31cdc26f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/WebsocketDispatcherConfig.java
@@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.config;
* #L%
*/
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionWebsocketHandler;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
index 22eb20d0a8b..38060e32461 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/BaseDstu3Config.java
@@ -123,7 +123,7 @@ public class BaseDstu3Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
- return new SearchParamRegistryDstu3(searchParamProvider());
+ return new SearchParamRegistryDstu3();
}
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
index fdc4e637a78..5224694a797 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/BaseR4Config.java
@@ -138,7 +138,7 @@ public class BaseR4Config extends BaseConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
- return new SearchParamRegistryR4(searchParamProvider());
+ return new SearchParamRegistryR4();
}
@Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
index dc3213931af..cc514088696 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java
@@ -2,16 +2,16 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.*;
-import ca.uhn.fhir.jpa.dao.index.*;
-import ca.uhn.fhir.jpa.model.entity.*;
+import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
+import ca.uhn.fhir.jpa.dao.index.IdHelperService;
+import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
import ca.uhn.fhir.jpa.entity.*;
+import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
-import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
-import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
@@ -59,7 +59,6 @@ import org.hibernate.internal.SessionImpl;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.springframework.beans.BeansException;
-import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@@ -78,8 +77,6 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;
-import java.io.CharArrayWriter;
-import java.text.Normalizer;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
@@ -174,23 +171,17 @@ public abstract class BaseHapiFhirDao implements IDao,
@Autowired
private ISearchDao mySearchDao;
@Autowired
- private ISearchParamExtractor mySearchParamExtractor;
- @Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc;
//@Autowired
//private ISearchResultDao mySearchResultDao;
@Autowired
- private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
- @Autowired
- private BeanFactory beanFactory;
- @Autowired
private DaoRegistry myDaoRegistry;
@Autowired
- private SearchParamExtractorService mySearchParamExtractorService;
- @Autowired
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
@Autowired
- private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer;
+ private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
+ @Autowired
+ private SearchBuilderFactory mySearchBuilderFactory;
private ApplicationContext myApplicationContext;
@@ -752,9 +743,9 @@ public abstract class BaseHapiFhirDao implements IDao,
return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId);
}
- @Override
+ // TODO KHS inject a searchBuilderFactory into callers of this method and delete this method
public SearchBuilder newSearchBuilder() {
- return beanFactory.getBean(SearchBuilder.class, this);
+ return mySearchBuilderFactory.newSearchBuilder(this);
}
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
@@ -1412,7 +1403,7 @@ public abstract class BaseHapiFhirDao implements IDao,
* Indexing
*/
if (thePerformIndexing) {
- myDatabaseSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams);
+ myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams);
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
index 56476f422fd..a8ef25c7bee 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java
@@ -24,13 +24,11 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
-import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
import ca.uhn.fhir.jpa.dao.r4.MatchResourceUrlService;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions;
@@ -73,17 +71,19 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseHapiFhirResourceDao extends BaseHapiFhirDao implements IFhirResourceDao {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
+
@Autowired
protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired(required = false)
protected IFulltextSearchSvc mySearchDao;
@Autowired
protected DaoConfig myDaoConfig;
+ @Autowired
+ private MatchResourceUrlService myMatchResourceUrlService;
+
private String myResourceName;
private Class myResourceType;
private String mySecondaryPrimaryKeyParamName;
- @Autowired
- private MatchResourceUrlService myMatchResourceUrlService;
@Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
index 022c4e3419d..76c7a03bab7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
@@ -4,12 +4,12 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
-import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets;
-import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils;
+import org.hl7.fhir.instance.model.Subscription;
import org.hl7.fhir.r4.model.Bundle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -112,7 +112,7 @@ public class DaoConfig {
* update setter javadoc if default changes
*/
private boolean myIndexContainedResources = true;
- private List myInterceptors;
+ private List myInterceptors = new ArrayList<>();
/**
* update setter javadoc if default changes
*/
@@ -484,14 +484,26 @@ public class DaoConfig {
* Returns the interceptors which will be notified of operations.
*
* @see #setInterceptors(List)
+ * @deprecated Marked as deprecated as of HAPI 3.7.0. Use {@link #registerInterceptor} or {@link #unregisterInterceptor}instead.
*/
+
+ @Deprecated
public List getInterceptors() {
- if (myInterceptors == null) {
- myInterceptors = new ArrayList<>();
- }
return myInterceptors;
}
+ public void registerInterceptor(IServerInterceptor theInterceptor) {
+ Validate.notNull(theInterceptor, "Interceptor can not be null");
+ if (!myInterceptors.contains(theInterceptor)) {
+ myInterceptors.add(theInterceptor);
+ }
+ }
+
+ public void unregisterInterceptor(IServerInterceptor theInterceptor) {
+ Validate.notNull(theInterceptor, "Interceptor can not be null");
+ myInterceptors.remove(theInterceptor);
+ }
+
/**
* This may be used to optionally register server interceptors directly against the DAOs.
*/
@@ -1462,6 +1474,47 @@ public class DaoConfig {
myModelConfig.setDefaultSearchParamsCanBeOverridden(theDefaultSearchParamsCanBeOverridden);
}
+ /**
+ * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
+ * to the server matching these types will be activated.
+ *
+ */
+ public DaoConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
+ myModelConfig.addSupportedSubscriptionType(theSubscriptionChannelType);
+ return this;
+ }
+
+ /**
+ * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
+ * to the server matching these types will be activated.
+ *
+ */
+ public Set getSupportedSubscriptionTypes() {
+ return myModelConfig.getSupportedSubscriptionTypes();
+ }
+
+ @VisibleForTesting
+ public void clearSupportedSubscriptionTypesForUnitTest() {
+ myModelConfig.clearSupportedSubscriptionTypesForUnitTest();
+ }
+
+ /**
+ * If e-mail subscriptions are supported, the From address used when sending e-mails
+ */
+
+ public String getEmailFromAddress() {
+ return myModelConfig.getEmailFromAddress();
+ }
+
+ /**
+ * If e-mail subscriptions are supported, the From address used when sending e-mails
+ */
+
+ public void setEmailFromAddress(String theEmailFromAddress) {
+ myModelConfig.setEmailFromAddress(theEmailFromAddress);
+ }
+
+
public enum IndexEnabledEnum {
ENABLED,
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java
similarity index 93%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java
index 0c57157a14e..9ae8f6807e7 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DatabaseSearchParamProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoSearchParamProvider.java
@@ -20,17 +20,19 @@ package ca.uhn.fhir.jpa.dao;
* #L%
*/
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
-public class DatabaseSearchParamProvider implements ISearchParamProvider {
+@Service
+public class DaoSearchParamProvider implements ISearchParamProvider {
@Autowired
private PlatformTransactionManager myTxManager;
@Autowired
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
similarity index 50%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
index 9eb56024454..c75d1b32dd1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketInterceptor.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilderFactory.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.websocket;
+package ca.uhn.fhir.jpa.dao;
/*-
* #%L
@@ -20,24 +20,11 @@ package ca.uhn.fhir.jpa.subscription.websocket;
* #L%
*/
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import org.hl7.fhir.r4.model.Subscription;
-import org.springframework.messaging.MessageHandler;
-
-import java.util.Optional;
-
-public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
-
- @Override
- protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) {
- return Optional.empty();
- }
-
- @Override
- public Subscription.SubscriptionChannelType getChannelType() {
- return Subscription.SubscriptionChannelType.WEBSOCKET;
- }
-
+import org.springframework.beans.factory.annotation.Lookup;
+import org.springframework.stereotype.Service;
+@Service
+public abstract class SearchBuilderFactory {
+ @Lookup
+ public abstract SearchBuilder newSearchBuilder(BaseHapiFhirDao theBaseHapiFhirResourceDao);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
similarity index 97%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
index fe920db14bc..c55d7430acb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseResourceLinkResolver.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoResourceLinkResolver.java
@@ -41,8 +41,8 @@ import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
@Service
-public class DatabaseResourceLinkResolver implements IResourceLinkResolver {
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DatabaseResourceLinkResolver.class);
+public class DaoResourceLinkResolver implements IResourceLinkResolver {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class);
@Autowired
private DaoConfig myDaoConfig;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
similarity index 98%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
index 27fe5d219f7..9e7a3ad1d66 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DatabaseSearchParamSynchronizer.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/DaoSearchParamSynchronizer.java
@@ -35,7 +35,7 @@ import java.util.Collection;
import java.util.List;
@Service
-public class DatabaseSearchParamSynchronizer {
+public class DaoSearchParamSynchronizer {
@Autowired
private DaoConfig myDaoConfig;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java
index b8ba6b4efb6..631eab984e8 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java
@@ -77,9 +77,9 @@ public class SearchParamWithInlineReferencesExtractor {
@Autowired
ResourceLinkExtractor myResourceLinkExtractor;
@Autowired
- DatabaseResourceLinkResolver myDatabaseResourceLinkResolver;
+ DaoResourceLinkResolver myDaoResourceLinkResolver;
@Autowired
- DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer;
+ DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@@ -99,7 +99,7 @@ public class SearchParamWithInlineReferencesExtractor {
extractInlineReferences(theResource);
- myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDatabaseResourceLinkResolver);
+ myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver);
/*
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
@@ -258,12 +258,12 @@ public class SearchParamWithInlineReferencesExtractor {
// Store composite string uniques
if (myDaoConfig.isUniqueIndexesEnabled()) {
- for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
+ for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().remove(next);
}
- for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
+ for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java
index 12ae5e73bab..95f862f081d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/DatabaseBackedPagingProvider.java
@@ -28,7 +28,9 @@ import ca.uhn.fhir.rest.server.IPagingProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-@Service
+// Note: this class is not annotated with @Service because we want to
+// explicitly define it in BaseConfig.java. This is done so that
+// implementors can override if they want to.
public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
@Autowired
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IResourceReindexingSvc.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IResourceReindexingSvc.java
index 7963413b83c..56a4d5d0876 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IResourceReindexingSvc.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/IResourceReindexingSvc.java
@@ -58,4 +58,6 @@ public interface IResourceReindexingSvc {
* to be used by unit tests.
*/
void cancelAndPurgeAllJobs();
+
+ int countReindexJobs();
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java
index 362fa8675e3..f9048b66d7a 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImpl.java
@@ -30,11 +30,11 @@ import ca.uhn.fhir.jpa.dao.data.IForcedIdDao;
import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
-import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
+import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
-import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting;
@@ -98,6 +98,8 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager;
+ @Autowired
+ private ISearchParamRegistry mySearchParamRegistry;
@VisibleForTesting
void setReindexJobDaoForUnitTest(IResourceReindexJobDao theReindexJobDao) {
@@ -186,7 +188,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
runReindexingPass();
}
-
@Override
@Transactional(Transactional.TxType.NEVER)
public Integer runReindexingPass() {
@@ -203,7 +204,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return null;
}
- private Integer doReindexingPassInsideLock() {
+ private int doReindexingPassInsideLock() {
expungeJobsMarkedAsDeleted();
return runReindexJobs();
}
@@ -233,13 +234,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
}
private int runReindexJobs() {
- Collection jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
- assert jobs != null;
+ Collection jobs = getResourceReindexJobEntities();
if (jobs.size() > 0) {
ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs);
} else {
ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs);
+ return 0;
}
int count = 0;
@@ -255,6 +256,17 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return count;
}
+ @Override
+ public int countReindexJobs() {
+ return getResourceReindexJobEntities().size();
+ }
+
+ private Collection getResourceReindexJobEntities() {
+ Collection jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
+ assert jobs != null;
+ return jobs;
+ }
+
private void markJobAsDeleted(ResourceReindexJobEntity theJob) {
ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId());
myTxTemplate.execute(t -> {
@@ -263,6 +275,11 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
});
}
+ @VisibleForTesting
+ public void setSearchParamRegistryForUnitTest(ISearchParamRegistry theSearchParamRegistry) {
+ mySearchParamRegistry = theSearchParamRegistry;
+ }
+
private int runReindexJob(ResourceReindexJobEntity theJob) {
if (theJob.getSuspendedUntil() != null) {
if (theJob.getSuspendedUntil().getTime() > System.currentTimeMillis()) {
@@ -274,6 +291,16 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
StopWatch sw = new StopWatch();
AtomicInteger counter = new AtomicInteger();
+ /*
+ * On the first time we run a particular reindex job, let's make sure we
+ * have the latest search parameters loaded. A common reason to
+ * be reindexing is that the search parameters have changed in some way, so
+ * this makes sure we're on the latest versions
+ */
+ if (theJob.getThresholdLow() == null) {
+ mySearchParamRegistry.forceRefresh();
+ }
+
// Calculate range
Date low = theJob.getThresholdLow() != null ? theJob.getThresholdLow() : BEGINNING_OF_TIME;
Date high = theJob.getThresholdHigh();
@@ -461,7 +488,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
IFhirResourceDao> dao = myDaoRegistry.getResourceDao(resourceTable.getResourceType());
long expectedVersion = resourceTable.getVersion();
- IBaseResource resource = dao.read(resourceTable.getIdDt().toVersionless(), null,true);
+ IBaseResource resource = dao.read(resourceTable.getIdDt().toVersionless(), null, true);
if (resource == null) {
throw new InternalErrorException("Could not find resource version " + resourceTable.getIdDt().toUnqualified().getValue() + " in database");
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java
deleted file mode 100644
index e1418316073..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java
+++ /dev/null
@@ -1,630 +0,0 @@
-package ca.uhn.fhir.jpa.subscription;
-
-import ca.uhn.fhir.context.ConfigurationException;
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.FhirVersionEnum;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.jpa.config.BaseConfig;
-import ca.uhn.fhir.jpa.dao.DaoRegistry;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
-import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
-import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherCompositeInMemoryDatabase;
-import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase;
-import ca.uhn.fhir.jpa.util.JpaConstants;
-import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import ca.uhn.fhir.rest.api.server.RequestDetails;
-import ca.uhn.fhir.rest.param.TokenOrListParam;
-import ca.uhn.fhir.rest.param.TokenParam;
-import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
-import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
-import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
-import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
-import ca.uhn.fhir.util.StopWatch;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Multimaps;
-import org.apache.commons.lang3.Validate;
-import org.apache.commons.lang3.concurrent.BasicThreadFactory;
-import org.hl7.fhir.exceptions.FHIRException;
-import org.hl7.fhir.instance.model.api.IBaseReference;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.Subscription;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.messaging.MessageChannel;
-import org.springframework.messaging.MessageHandler;
-import org.springframework.messaging.SubscribableChannel;
-import org.springframework.messaging.support.ExecutorSubscribableChannel;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallbackWithoutResult;
-import org.springframework.transaction.support.TransactionSynchronizationAdapter;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.PreDestroy;
-import java.util.*;
-import java.util.concurrent.*;
-
-/*-
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2018 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-public abstract class BaseSubscriptionInterceptor extends ServerOperationInterceptorAdapter {
-
- static final String SUBSCRIPTION_STATUS = "Subscription.status";
- static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
- private static final Integer MAX_SUBSCRIPTION_RESULTS = 1000;
- private static boolean ourForcePayloadEncodeAndDecodeForUnitTests;
- private final Object myInitSubscriptionsLock = new Object();
- private SubscribableChannel myProcessingChannel;
- private Map myDeliveryChannel;
- private ExecutorService myProcessingExecutor;
- private int myExecutorThreadCount;
- private SubscriptionActivatingSubscriber mySubscriptionActivatingSubscriber;
- private MessageHandler mySubscriptionCheckingSubscriber;
- private ConcurrentHashMap myIdToSubscription = new ConcurrentHashMap<>();
- private ConcurrentHashMap mySubscribableChannel = new ConcurrentHashMap<>();
- private Multimap myIdToDeliveryHandler = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
- private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
- private ThreadPoolExecutor myDeliveryExecutor;
- private LinkedBlockingQueue myProcessingExecutorQueue;
- @Autowired
- private FhirContext myCtx;
- @Autowired(required = false)
- @Qualifier("myEventDefinitionDaoR4")
- private IFhirResourceDao myEventDefinitionDaoR4;
- @Autowired()
- private PlatformTransactionManager myTxManager;
- @Autowired
- @Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
- private AsyncTaskExecutor myAsyncTaskExecutor;
- @Autowired
- private SubscriptionMatcherCompositeInMemoryDatabase mySubscriptionMatcherCompositeInMemoryDatabase;
- @Autowired
- private SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
- @Autowired
- private DaoRegistry myDaoRegistry;
- @Autowired
- private BeanFactory beanFactory;
- @Autowired
- private MatchUrlService myMatchUrlService;
- private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
-
- /**
- * Constructor
- */
- public BaseSubscriptionInterceptor() {
- super();
- setExecutorThreadCount(5);
- }
-
- protected CanonicalSubscription canonicalize(S theSubscription) {
- switch (myCtx.getVersion().getVersion()) {
- case DSTU2:
- return canonicalizeDstu2(theSubscription);
- case DSTU3:
- return canonicalizeDstu3(theSubscription);
- case R4:
- return canonicalizeR4(theSubscription);
- default:
- throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion());
- }
- }
-
- protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
- ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
-
- CanonicalSubscription retVal = new CanonicalSubscription();
- try {
- retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
- retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
- retVal.setCriteriaString(subscription.getCriteria());
- retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
- retVal.setHeaders(subscription.getChannel().getHeader());
- retVal.setIdElement(subscription.getIdElement());
- retVal.setPayloadString(subscription.getChannel().getPayload());
- } catch (FHIRException theE) {
- throw new InternalErrorException(theE);
- }
- return retVal;
- }
-
- protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
- org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
-
- CanonicalSubscription retVal = new CanonicalSubscription();
- try {
- retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
- retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
- retVal.setCriteriaString(subscription.getCriteria());
- retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
- retVal.setHeaders(subscription.getChannel().getHeader());
- retVal.setIdElement(subscription.getIdElement());
- retVal.setPayloadString(subscription.getChannel().getPayload());
-
- if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) {
- String from;
- String subjectTemplate;
- String bodyTemplate;
- try {
- from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
- subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
- } catch (FHIRException theE) {
- throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
- }
- retVal.getEmailDetails().setFrom(from);
- retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
- }
-
- if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) {
- String stripVersionIds;
- String deliverLatestVersion;
- try {
- stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
- deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
- } catch (FHIRException theE) {
- throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
- }
- retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
- retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
- }
-
- } catch (FHIRException theE) {
- throw new InternalErrorException(theE);
- }
- return retVal;
- }
-
- protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
- org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
-
- CanonicalSubscription retVal = new CanonicalSubscription();
- retVal.setStatus(subscription.getStatus());
- retVal.setChannelType(subscription.getChannel().getType());
- retVal.setCriteriaString(subscription.getCriteria());
- retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
- retVal.setHeaders(subscription.getChannel().getHeader());
- retVal.setIdElement(subscription.getIdElement());
- retVal.setPayloadString(subscription.getChannel().getPayload());
-
- if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) {
- String from;
- String subjectTemplate;
- try {
- from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
- subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
- } catch (FHIRException theE) {
- throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
- }
- retVal.getEmailDetails().setFrom(from);
- retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
- }
-
- if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) {
- String stripVersionIds;
- String deliverLatestVersion;
- try {
- stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
- deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
- } catch (FHIRException theE) {
- throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
- }
- retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
- retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
- }
-
- List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
- if (topicExts.size() > 0) {
- IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
- if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
- throw new PreconditionFailedException("Topic reference must be an EventDefinition");
- }
-
- org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement());
- retVal.addTrigger(new CanonicalSubscription.CanonicalEventDefinition(def));
- }
-
- return retVal;
- }
-
- protected SubscribableChannel createDeliveryChannel(CanonicalSubscription theSubscription) {
- String subscriptionId = theSubscription.getIdElement(myCtx).getIdPart();
-
- LinkedBlockingQueue executorQueue = new LinkedBlockingQueue<>(1000);
- BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
- .namingPattern("subscription-delivery-" + subscriptionId + "-%d")
- .daemon(false)
- .priority(Thread.NORM_PRIORITY)
- .build();
- RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
- @Override
- public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) {
- ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", executorQueue.size());
- StopWatch sw = new StopWatch();
- try {
- executorQueue.put(theRunnable);
- } catch (InterruptedException theE) {
- throw new RejectedExecutionException("Task " + theRunnable.toString() +
- " rejected from " + theE.toString());
- }
- ourLog.info("Slot become available after {}ms", sw.getMillis());
- }
- };
- ThreadPoolExecutor deliveryExecutor = new ThreadPoolExecutor(
- 1,
- getExecutorThreadCount(),
- 0L,
- TimeUnit.MILLISECONDS,
- executorQueue,
- threadFactory,
- rejectedExecutionHandler);
-
- return new ExecutorSubscribableChannel(deliveryExecutor);
- }
-
- /**
- * Returns an empty handler if the interceptor will manually handle registration and unregistration
- */
- protected abstract Optional createDeliveryHandler(CanonicalSubscription theSubscription);
-
- public abstract Subscription.SubscriptionChannelType getChannelType();
-
- protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) {
- return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart());
- }
-
- public int getExecutorQueueSizeForUnitTests() {
- return myProcessingExecutorQueue.size();
- }
-
- public int getExecutorThreadCount() {
- return myExecutorThreadCount;
- }
-
- public void setExecutorThreadCount(int theExecutorThreadCount) {
- Validate.inclusiveBetween(1, Integer.MAX_VALUE, theExecutorThreadCount);
- myExecutorThreadCount = theExecutorThreadCount;
- }
-
- public Map getIdToSubscription() {
- return Collections.unmodifiableMap(myIdToSubscription);
- }
-
- public SubscribableChannel getProcessingChannel() {
- return myProcessingChannel;
- }
-
- public void setProcessingChannel(SubscribableChannel theProcessingChannel) {
- myProcessingChannel = theProcessingChannel;
- }
-
- public List getRegisteredSubscriptions() {
- return new ArrayList<>(myIdToSubscription.values());
- }
-
- public CanonicalSubscription hasSubscription(IIdType theId) {
- Validate.notNull(theId);
- Validate.notBlank(theId.getIdPart());
- return myIdToSubscription.get(theId.getIdPart());
- }
-
- /**
- * Read the existing subscriptions from the database
- */
- @SuppressWarnings("unused")
- @Scheduled(fixedDelay = 60000)
- public void initSubscriptions() {
- if (!myInitSubscriptionsSemaphore.tryAcquire()) {
- return;
- }
- try {
- doInitSubscriptions();
- } finally {
- myInitSubscriptionsSemaphore.release();
- }
- }
-
- public Integer doInitSubscriptions() {
- synchronized (myInitSubscriptionsLock) {
- ourLog.debug("Starting init subscriptions");
- SearchParameterMap map = new SearchParameterMap();
- map.add(Subscription.SP_TYPE, new TokenParam(null, getChannelType().toCode()));
- map.add(Subscription.SP_STATUS, new TokenOrListParam()
- .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
- .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
- map.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS);
-
- RequestDetails req = new ServletSubRequestDetails();
- req.setSubRequest(true);
-
- IFhirResourceDao> subscriptionDao = myDaoRegistry.getSubscriptionDao();
- IBundleProvider subscriptionBundleList = subscriptionDao.search(map, req);
- if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
- ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
- }
-
- List resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
-
- Set allIds = new HashSet<>();
- int changesCount = 0;
- for (IBaseResource resource : resourceList) {
- String nextId = resource.getIdElement().getIdPart();
- allIds.add(nextId);
- boolean changed = mySubscriptionActivatingSubscriber.activateOrRegisterSubscriptionIfRequired(resource);
- if (changed) {
- changesCount++;
- }
- }
-
- unregisterAllSubscriptionsNotInCollection(allIds);
- ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
-
- return changesCount;
- }
- }
-
- @SuppressWarnings("unused")
- @PreDestroy
- public void preDestroy() {
- getProcessingChannel().unsubscribe(mySubscriptionCheckingSubscriber);
- unregisterAllSubscriptionsNotInCollection(Collections.emptyList());
- }
-
- public void registerHandler(String theSubscriptionId, MessageHandler theHandler) {
- mySubscribableChannel.get(theSubscriptionId).subscribe(theHandler);
- myIdToDeliveryHandler.put(theSubscriptionId, theHandler);
- }
-
- @SuppressWarnings("UnusedReturnValue")
- public CanonicalSubscription registerSubscription(IIdType theId, S theSubscription) {
- Validate.notNull(theId);
- String subscriptionId = theId.getIdPart();
- Validate.notBlank(subscriptionId);
- Validate.notNull(theSubscription);
-
- CanonicalSubscription canonicalized = canonicalize(theSubscription);
- SubscribableChannel deliveryChannel = createDeliveryChannel(canonicalized);
- Optional deliveryHandler = createDeliveryHandler(canonicalized);
-
- mySubscribableChannel.put(subscriptionId, deliveryChannel);
- myIdToSubscription.put(subscriptionId, canonicalized);
-
- deliveryHandler.ifPresent(handler -> registerHandler(subscriptionId, handler));
-
- return canonicalized;
- }
-
- protected void registerSubscriptionCheckingSubscriber() {
- if (mySubscriptionCheckingSubscriber == null) {
- mySubscriptionCheckingSubscriber = beanFactory.getBean(SubscriptionCheckingSubscriber.class, getChannelType(), this, mySubscriptionMatcherCompositeInMemoryDatabase);
- }
- getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
- }
-
- @Override
- public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
- submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
- }
-
- @Override
- public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
- ResourceModifiedMessage msg = new ResourceModifiedMessage();
- msg.setId(theResource.getIdElement());
- msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.DELETE);
- submitResourceModified(msg);
- }
-
- @Override
- public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
- submitResourceModifiedForUpdate(theNewResource);
- }
-
- void submitResourceModifiedForUpdate(IBaseResource theNewResource) {
- submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
- }
-
- private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
- ResourceModifiedMessage msg = new ResourceModifiedMessage();
- msg.setId(theNewResource.getIdElement());
- msg.setOperationType(theOperationType);
- msg.setNewPayload(myCtx, theNewResource);
- if (ourForcePayloadEncodeAndDecodeForUnitTests) {
- msg.clearPayloadDecoded();
- }
- submitResourceModified(msg);
- }
-
- protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
- ourLog.trace("Registering synchronization to send resource modified message to processing channel");
-
- /*
- * We only actually submit this item work working after the
- */
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
- @Override
- public void afterCommit() {
- ourLog.trace("Sending resource modified message to processing channel");
- getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
- }
- });
- } else {
- ourLog.trace("Sending resource modified message to processing channel");
- getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
- }
- }
-
- @VisibleForTesting
- public void setAsyncTaskExecutorForUnitTest(AsyncTaskExecutor theAsyncTaskExecutor) {
- myAsyncTaskExecutor = theAsyncTaskExecutor;
- }
-
- public void setFhirContext(FhirContext theCtx) {
- myCtx = theCtx;
- }
-
- @VisibleForTesting
- public void setTxManager(PlatformTransactionManager theTxManager) {
- myTxManager = theTxManager;
- }
-
- @PostConstruct
- public void start() {
- if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
- Validate.notNull(myEventDefinitionDaoR4);
- }
-
- if (getProcessingChannel() == null) {
- myProcessingExecutorQueue = new LinkedBlockingQueue<>(1000);
- RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> {
- ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", myProcessingExecutorQueue.size());
- StopWatch sw = new StopWatch();
- try {
- myProcessingExecutorQueue.put(theRunnable);
- } catch (InterruptedException theE) {
- throw new RejectedExecutionException("Task " + theRunnable.toString() +
- " rejected from " + theE.toString());
- }
- ourLog.info("Slot become available after {}ms", sw.getMillis());
- };
- ThreadFactory threadFactory = new BasicThreadFactory.Builder()
- .namingPattern("subscription-proc-%d")
- .daemon(false)
- .priority(Thread.NORM_PRIORITY)
- .build();
- myProcessingExecutor = new ThreadPoolExecutor(
- 1,
- getExecutorThreadCount(),
- 0L,
- TimeUnit.MILLISECONDS,
- myProcessingExecutorQueue,
- threadFactory,
- rejectedExecutionHandler);
- setProcessingChannel(new ExecutorSubscribableChannel(myProcessingExecutor));
- }
-
- if (mySubscriptionActivatingSubscriber == null) {
- IFhirResourceDao> subscriptionDao = myDaoRegistry.getSubscriptionDao();
- mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(subscriptionDao, getChannelType(), this, myTxManager, myAsyncTaskExecutor);
- }
-
- registerSubscriptionCheckingSubscriber();
-
- TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager);
- transactionTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- initSubscriptions();
- }
- });
- }
-
- /**
- * This is an internal API - Use with caution!
- */
- public void submitResourceModified(final ResourceModifiedMessage theMsg) {
- mySubscriptionActivatingSubscriber.handleMessage(theMsg.getOperationType(), theMsg.getId(myCtx), theMsg.getNewPayload(myCtx));
- sendToProcessingChannel(theMsg);
- }
-
- private void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) {
- for (String next : new ArrayList<>(myIdToSubscription.keySet())) {
- if (!theAllIds.contains(next)) {
- ourLog.info("Unregistering Subscription/{}", next);
- CanonicalSubscription subscription = myIdToSubscription.get(next);
- unregisterSubscription(subscription.getIdElement(myCtx));
- }
- }
- }
-
- public void unregisterHandler(String theSubscriptionId, MessageHandler theMessageHandler) {
- SubscribableChannel channel = mySubscribableChannel.get(theSubscriptionId);
- if (channel != null) {
- channel.unsubscribe(theMessageHandler);
- if (channel instanceof DisposableBean) {
- try {
- ((DisposableBean) channel).destroy();
- } catch (Exception e) {
- ourLog.error("Failed to destroy channel bean", e);
- }
- }
- }
-
- mySubscribableChannel.remove(theSubscriptionId);
- }
-
- @SuppressWarnings("UnusedReturnValue")
- public CanonicalSubscription unregisterSubscription(IIdType theId) {
- Validate.notNull(theId);
-
- String subscriptionId = theId.getIdPart();
- Validate.notBlank(subscriptionId);
-
- for (MessageHandler next : new ArrayList<>(myIdToDeliveryHandler.get(subscriptionId))) {
- unregisterHandler(subscriptionId, next);
- }
-
- mySubscribableChannel.remove(subscriptionId);
-
- return myIdToSubscription.remove(subscriptionId);
- }
-
- public IFhirResourceDao> getSubscriptionDao() {
- return myDaoRegistry.getSubscriptionDao();
- }
-
- public IFhirResourceDao getDao(Class type) {
- return myDaoRegistry.getResourceDao(type);
- }
-
- public void setResourceDaos(List theResourceDaos) {
- myDaoRegistry.setResourceDaos(theResourceDaos);
- }
-
- public void validateCriteria(final S theResource) {
- CanonicalSubscription subscription = canonicalize(theResource);
- String criteria = subscription.getCriteriaString();
- try {
- RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myCtx, criteria);
- myMatchUrlService.translateMatchUrl(criteria, resourceDef);
- } catch (InvalidRequestException e) {
- throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
- }
- }
-
- @VisibleForTesting
- public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) {
- ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests;
- }
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java
deleted file mode 100644
index 3a7d3fff7fc..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionSubscriber.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package ca.uhn.fhir.jpa.subscription;
-
-/*-
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2018 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.dao.DaoRegistry;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
-import org.hl7.fhir.r4.model.Subscription;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.MessageHandler;
-
-import javax.annotation.PostConstruct;
-
-public abstract class BaseSubscriptionSubscriber implements MessageHandler {
-
- private final Subscription.SubscriptionChannelType myChannelType;
- private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
- @Autowired
- DaoRegistry myDaoRegistry;
- private IFhirResourceDao> mySubscriptionDao;
-
- /**
- * Constructor
- */
- public BaseSubscriptionSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
- myChannelType = theChannelType;
- mySubscriptionInterceptor = theSubscriptionInterceptor;
- }
-
- @SuppressWarnings("unused") // Don't delete, used in Smile
- public void setDaoRegistry(DaoRegistry theDaoRegistry) {
- myDaoRegistry = theDaoRegistry;
- }
-
- @PostConstruct
- public void setSubscriptionDao() {
- mySubscriptionDao = myDaoRegistry.getSubscriptionDao();
- }
-
- public Subscription.SubscriptionChannelType getChannelType() {
- return myChannelType;
- }
-
- public FhirContext getContext() {
- return getSubscriptionDao().getContext();
- }
-
- public IFhirResourceDao getSubscriptionDao() {
- return mySubscriptionDao;
- }
-
- public BaseSubscriptionInterceptor getSubscriptionInterceptor() {
- return mySubscriptionInterceptor;
- }
-
-
- /**
- * Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor?
- */
- protected boolean subscriptionTypeApplies(CanonicalSubscription theSubscription) {
- Subscription.SubscriptionChannelType channelType = getChannelType();
- String subscriptionType = theSubscription.getChannelType().toCode();
- return subscriptionTypeApplies(subscriptionType, channelType);
- }
-
- /**
- * Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor?
- */
- static boolean subscriptionTypeApplies(String theSubscriptionChannelTypeCode, Subscription.SubscriptionChannelType theChannelType) {
- boolean subscriptionTypeApplies = false;
- if (theSubscriptionChannelTypeCode != null) {
- if (theChannelType.toCode().equals(theSubscriptionChannelTypeCode)) {
- subscriptionTypeApplies = true;
- }
- }
- return subscriptionTypeApplies;
- }
-
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java
new file mode 100644
index 00000000000..3294d055600
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/DaoResourceRetriever.java
@@ -0,0 +1,52 @@
+package ca.uhn.fhir.jpa.subscription;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever;
+import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DaoResourceRetriever implements IResourceRetriever {
+ private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
+
+ @Autowired
+ FhirContext myFhirContext;
+ @Autowired
+ DaoRegistry myDaoRegistry;
+
+ @Override
+ public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException {
+ RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType());
+ IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceDef.getImplementingClass());
+ return dao.read(payloadId.toVersionless());
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java
new file mode 100644
index 00000000000..6563d8bbd31
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingInterceptor.java
@@ -0,0 +1,247 @@
+package ca.uhn.fhir.jpa.subscription;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.config.BaseConfig;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
+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.cache.SubscriptionCannonicalizer;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
+import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
+import ca.uhn.fhir.util.SubscriptionUtil;
+import com.google.common.annotations.VisibleForTesting;
+import org.hl7.fhir.instance.model.Subscription;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.core.task.AsyncTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionSynchronizationAdapter;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Responsible for transitioning subscription resources from REQUESTED to ACTIVE
+ * Once activated, the subscription is added to the SubscriptionRegistry.
+ *
+ * Also validates criteria. If invalid, rejects the subscription without persisting the subscription.
+ */
+@Service
+public class SubscriptionActivatingInterceptor extends ServerOperationInterceptorAdapter {
+ private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class);
+
+ private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest;
+
+ @Autowired
+ private PlatformTransactionManager myTransactionManager;
+ @Autowired
+ @Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
+ private AsyncTaskExecutor myTaskExecutor;
+ @Autowired
+ private SubscriptionRegistry mySubscriptionRegistry;
+ @Autowired
+ private DaoRegistry myDaoRegistry;
+ @Autowired
+ private FhirContext myFhirContext;
+ @Autowired
+ private SubscriptionCannonicalizer mySubscriptionCannonicalizer;
+ @Autowired
+ private MatchUrlService myMatchUrlService;
+ @Autowired
+ private DaoConfig myDaoConfig;
+
+ public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
+ // Grab the value for "Subscription.channel.type" so we can see if this
+ // subscriber applies..
+ String subscriptionChannelTypeCode = myFhirContext
+ .newTerser()
+ .getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class)
+ .getValueAsString();
+
+ Subscription.SubscriptionChannelType subscriptionChannelType = Subscription.SubscriptionChannelType.fromCode(subscriptionChannelTypeCode);
+ // Only activate supported subscriptions
+ if (!myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType)) {
+ return false;
+ }
+
+ final IPrimitiveType> status = myFhirContext.newTerser().getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
+ String statusString = status.getValueAsString();
+
+ final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
+ final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode();
+ if (requestedStatus.equals(statusString)) {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ /*
+ * If we're in a transaction, we don't want to try and change the status from
+ * requested to active within the same transaction because it's too late by
+ * the time we get here to make modifications to the payload.
+ *
+ * So, we register a synchronization, meaning that when the transaction is
+ * finished, we'll schedule a task to do this in a separate worker thread
+ * to avoid any possibility of conflict.
+ */
+ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
+ @Override
+ public void afterCommit() {
+ Future> activationFuture = myTaskExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ activateSubscription(activeStatus, theSubscription, requestedStatus);
+ }
+ });
+
+ /*
+ * If we're running in a unit test, it's nice to be predictable in
+ * terms of order... In the real world it's a recipe for deadlocks
+ */
+ if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) {
+ try {
+ activationFuture.get(5, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ ourLog.error("Failed to activate subscription", e);
+ }
+ }
+ }
+ });
+ return true;
+ } else {
+ return activateSubscription(activeStatus, theSubscription, requestedStatus);
+ }
+ } else if (activeStatus.equals(statusString)) {
+ return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription);
+ } else {
+ // Status isn't "active" or "requested"
+ return mySubscriptionRegistry.unregisterSubscriptionIfRegistered(theSubscription, statusString);
+ }
+ }
+
+
+ private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
+ IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
+ IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement());
+
+ ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
+ try {
+ SubscriptionUtil.setStatus(myFhirContext, subscription, theActiveStatus);
+ subscription = subscriptionDao.update(subscription).getResource();
+ submitResourceModifiedForUpdate(subscription);
+ return true;
+ } catch (final UnprocessableEntityException e) {
+ ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
+ SubscriptionUtil.setStatus(myFhirContext, subscription, "error");
+ SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage());
+ subscriptionDao.update(subscription);
+ return false;
+ }
+ }
+
+ void submitResourceModifiedForUpdate(IBaseResource theNewResource) {
+ submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
+ }
+
+ @Override
+ public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
+ submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
+ }
+
+ @Override
+ public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
+ submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
+ }
+
+ @Override
+ public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
+ submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
+ }
+
+ private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
+ submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
+ }
+
+ private void submitResourceModified(final ResourceModifiedMessage theMsg) {
+ IIdType id = theMsg.getId(myFhirContext);
+ if (!id.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
+ return;
+ }
+ switch (theMsg.getOperationType()) {
+ case DELETE:
+ mySubscriptionRegistry.unregisterSubscription(id);
+ break;
+ case CREATE:
+ case UPDATE:
+ final IBaseResource subscription = theMsg.getNewPayload(myFhirContext);
+ validateCriteria(subscription);
+ activateAndRegisterSubscriptionIfRequiredInTransaction(subscription);
+ break;
+ default:
+ break;
+ }
+ }
+
+ public void validateCriteria(final IBaseResource theResource) {
+ CanonicalSubscription subscription = mySubscriptionCannonicalizer.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) {
+ TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
+ txTemplate.execute(new TransactionCallbackWithoutResult() {
+ @Override
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ activateOrRegisterSubscriptionIfRequired(theSubscription);
+ }
+ });
+ }
+
+
+ @VisibleForTesting
+ public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) {
+ ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest;
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java
deleted file mode 100644
index 44f488b9c54..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java
+++ /dev/null
@@ -1,220 +0,0 @@
-package ca.uhn.fhir.jpa.subscription;
-
-/*-
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2018 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
-import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.util.SubscriptionUtil;
-import com.google.common.annotations.VisibleForTesting;
-import org.apache.commons.lang3.Validate;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.instance.model.api.IPrimitiveType;
-import org.hl7.fhir.r4.model.Subscription;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.core.task.AsyncTaskExecutor;
-import org.springframework.messaging.MessagingException;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.TransactionStatus;
-import org.springframework.transaction.support.TransactionCallbackWithoutResult;
-import org.springframework.transaction.support.TransactionSynchronizationAdapter;
-import org.springframework.transaction.support.TransactionSynchronizationManager;
-import org.springframework.transaction.support.TransactionTemplate;
-
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-@SuppressWarnings("unchecked")
-public class SubscriptionActivatingSubscriber {
- private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest;
- private final IFhirResourceDao mySubscriptionDao;
- private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
- private final PlatformTransactionManager myTransactionManager;
- private final AsyncTaskExecutor myTaskExecutor;
- private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class);
- private FhirContext myCtx;
- private Subscription.SubscriptionChannelType myChannelType;
-
-
- /**
- * Constructor
- */
- public SubscriptionActivatingSubscriber(IFhirResourceDao extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager, AsyncTaskExecutor theTaskExecutor) {
- mySubscriptionDao = theSubscriptionDao;
- mySubscriptionInterceptor = theSubscriptionInterceptor;
- myChannelType = theChannelType;
- myCtx = theSubscriptionDao.getContext();
- myTransactionManager = theTransactionManager;
- myTaskExecutor = theTaskExecutor;
- Validate.notNull(theTaskExecutor);
- }
-
- public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
- // Grab the value for "Subscription.channel.type" so we can see if this
- // subscriber applies..
- String subscriptionChannelType = myCtx
- .newTerser()
- .getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class)
- .getValueAsString();
- boolean subscriptionTypeApplies = BaseSubscriptionSubscriber.subscriptionTypeApplies(subscriptionChannelType, myChannelType);
- if (subscriptionTypeApplies == false) {
- return false;
- }
-
- final IPrimitiveType> status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
- String statusString = status.getValueAsString();
-
- final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
- final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode();
- if (requestedStatus.equals(statusString)) {
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- /*
- * If we're in a transaction, we don't want to try and change the status from
- * requested to active within the same transaction because it's too late by
- * the time we get here to make modifications to the payload.
- *
- * So, we register a synchronization, meaning that when the transaction is
- * finished, we'll schedule a task to do this in a separate worker thread
- * to avoid any possibility of conflict.
- */
- TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
- @Override
- public void afterCommit() {
- Future> activationFuture = myTaskExecutor.submit(new Runnable() {
- @Override
- public void run() {
- activateSubscription(activeStatus, theSubscription, requestedStatus);
- }
- });
-
- /*
- * If we're running in a unit test, it's nice to be predictable in
- * terms of order... In the real world it's a recipe for deadlocks
- */
- if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) {
- try {
- activationFuture.get(5, TimeUnit.SECONDS);
- } catch (Exception e) {
- ourLog.error("Failed to activate subscription", e);
- }
- }
- }
- });
- return true;
- } else {
- return activateSubscription(activeStatus, theSubscription, requestedStatus);
- }
- } else if (activeStatus.equals(statusString)) {
- return registerSubscriptionUnlessAlreadyRegistered(theSubscription);
- } else {
- // Status isn't "active" or "requested"
- return unregisterSubscriptionIfRegistered(theSubscription, statusString);
- }
- }
-
- protected boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) {
- if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement()) != null) {
- ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue());
- mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
- return true;
- }
- return false;
- }
-
- private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
- IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement());
-
- ourLog.info("Activating subscription {} from status {} to {} for channel {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus, myChannelType);
- try {
- SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus);
- subscription = mySubscriptionDao.update(subscription).getResource();
- mySubscriptionInterceptor.submitResourceModifiedForUpdate(subscription);
- return true;
- } catch (final UnprocessableEntityException e) {
- ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
- SubscriptionUtil.setStatus(myCtx, subscription, "error");
- SubscriptionUtil.setReason(myCtx, subscription, e.getMessage());
- mySubscriptionDao.update(subscription);
- return false;
- }
-
- }
-
- @SuppressWarnings("EnumSwitchStatementWhichMissesCases")
- public void handleMessage(ResourceModifiedMessage.OperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
- if (!theId.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
- return;
- }
- switch (theOperationType) {
- case DELETE:
- mySubscriptionInterceptor.unregisterSubscription(theId);
- break;
- case CREATE:
- case UPDATE:
- mySubscriptionInterceptor.validateCriteria(theSubscription);
- activateAndRegisterSubscriptionIfRequiredInTransaction(theSubscription);
- break;
- default:
- break;
- }
-
- }
-
- private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
- TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
- txTemplate.execute(new TransactionCallbackWithoutResult() {
- @Override
- protected void doInTransactionWithoutResult(TransactionStatus status) {
- activateOrRegisterSubscriptionIfRequired(theSubscription);
- }
- });
- }
-
- protected synchronized boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) {
- CanonicalSubscription existingSubscription = mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement());
- CanonicalSubscription newSubscription = mySubscriptionInterceptor.canonicalize(theSubscription);
-
- if (existingSubscription != null) {
- if (newSubscription.equals(existingSubscription)) {
- // No changes
- return false;
- }
- }
-
- if (existingSubscription != null) {
- ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
- mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
- } else {
- ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
- }
- mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription);
- return true;
- }
-
- @VisibleForTesting
- public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) {
- ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest;
- }
-
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java
new file mode 100644
index 00000000000..50b9750428b
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionInterceptorLoader.java
@@ -0,0 +1,54 @@
+package ca.uhn.fhir.jpa.subscription;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import com.google.common.annotations.VisibleForTesting;
+import org.hl7.fhir.instance.model.Subscription;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Set;
+
+@Service
+public class SubscriptionInterceptorLoader {
+ @Autowired
+ DaoConfig myDaoConfig;
+ @Autowired
+ SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
+ @Autowired
+ SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor;
+
+ public void registerInterceptors() {
+ Set supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes();
+
+ if (!supportedSubscriptionTypes.isEmpty()) {
+ myDaoConfig.registerInterceptor(mySubscriptionActivatingInterceptor);
+ myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
+ }
+ }
+
+ @VisibleForTesting
+ public void unregisterInterceptorsForUnitTest() {
+ myDaoConfig.unregisterInterceptor(mySubscriptionActivatingInterceptor);
+ myDaoConfig.unregisterInterceptor(mySubscriptionMatcherInterceptor);
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java
new file mode 100644
index 00000000000..f07fd0b3ad0
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionMatcherInterceptor.java
@@ -0,0 +1,127 @@
+package ca.uhn.fhir.jpa.subscription;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriber;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
+import com.google.common.annotations.VisibleForTesting;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+@Component
+public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAdapter {
+ private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class);
+
+ static final String SUBSCRIPTION_STATUS = "Subscription.status";
+ static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
+ private static boolean ourForcePayloadEncodeAndDecodeForUnitTests;
+ private SubscribableChannel myProcessingChannel;
+
+ @Autowired
+ private FhirContext myFhirContext;
+ @Autowired
+ private SubscriptionCheckingSubscriber mySubscriptionCheckingSubscriber;
+ @Autowired
+ private ISubscriptionChannelFactory mySubscriptionChannelFactory;
+
+ /**
+ * Constructor
+ */
+ public SubscriptionMatcherInterceptor() {
+ super();
+ }
+
+ @PostConstruct
+ public void start() {
+ if (myProcessingChannel == null) {
+ myProcessingChannel = mySubscriptionChannelFactory.newMatchingChannel("subscription-matching");
+ }
+ myProcessingChannel.subscribe(mySubscriptionCheckingSubscriber);
+ }
+
+ @SuppressWarnings("unused")
+ @PreDestroy
+ public void preDestroy() {
+ myProcessingChannel.unsubscribe(mySubscriptionCheckingSubscriber);
+ }
+
+ @Override
+ public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
+ submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
+ }
+
+ @Override
+ public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
+ submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
+ }
+
+ @Override
+ public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
+ submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
+ }
+
+ private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType);
+ if (ourForcePayloadEncodeAndDecodeForUnitTests) {
+ msg.clearPayloadDecoded();
+ }
+ submitResourceModified(msg);
+ }
+
+ protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
+ ourLog.trace("Sending resource modified message to processing channel");
+ myProcessingChannel.send(new ResourceModifiedJsonMessage(theMessage));
+ }
+
+ public void setFhirContext(FhirContext theCtx) {
+ myFhirContext = theCtx;
+ }
+
+ /**
+ * This is an internal API - Use with caution!
+ */
+ public void submitResourceModified(final ResourceModifiedMessage theMsg) {
+ sendToProcessingChannel(theMsg);
+ }
+
+ @VisibleForTesting
+ public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) {
+ ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests;
+ }
+
+ @VisibleForTesting
+ public SubscriptionChannel getProcessingChannelForUnitTest() {
+ return (SubscriptionChannel) myProcessingChannel;
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java
index 96bd3f9c032..01893c8a7e0 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringSvcImpl.java
@@ -22,13 +22,16 @@ package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
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.SearchParameterMap;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.api.CacheControlDirective;
@@ -38,6 +41,7 @@ import ca.uhn.fhir.rest.param.UriParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
+import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.ValidateUtil;
@@ -71,26 +75,31 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service
public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware {
+ private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
public static final int DEFAULT_MAX_SUBMIT = 10000;
- private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
- private final List myActiveJobs = new ArrayList<>();
+
@Autowired
private FhirContext myFhirContext;
@Autowired
private DaoRegistry myDaoRegistry;
- private List> mySubscriptionInterceptorList;
- private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
+ @Autowired
+ private DaoConfig myDaoConfig;
@Autowired
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired
private MatchUrlService myMatchUrlService;
+ @Autowired
+ private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
+
+ private final List myActiveJobs = new ArrayList<>();
+ private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
private ApplicationContext myAppCtx;
private ExecutorService myExecutorService;
@Override
public IBaseParameters triggerSubscription(List theResourceIds, List theSearchUrls, @IdParam IIdType theSubscriptionId) {
- if (mySubscriptionInterceptorList.isEmpty()) {
+ if (myDaoConfig.getSupportedSubscriptionTypes().isEmpty()) {
throw new PreconditionFailedException("Subscription processing not active on this server");
}
@@ -293,18 +302,13 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId);
- ResourceModifiedMessage msg = new ResourceModifiedMessage();
- msg.setId(theResourceToTrigger.getIdElement());
- msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE);
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResourceToTrigger, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue());
- msg.setNewPayload(myFhirContext, theResourceToTrigger);
return myExecutorService.submit(() -> {
for (int i = 0; ; i++) {
try {
- for (BaseSubscriptionInterceptor> next : mySubscriptionInterceptorList) {
- next.submitResourceModified(msg);
- }
+ mySubscriptionMatcherInterceptor.submitResourceModified(msg);
break;
} catch (Exception e) {
if (i >= 3) {
@@ -347,13 +351,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
@SuppressWarnings("unchecked")
@PostConstruct
public void start() {
- mySubscriptionInterceptorList = ObjectUtils.defaultIfNull(mySubscriptionInterceptorList, Collections.emptyList());
- mySubscriptionInterceptorList = new ArrayList<>();
- Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values();
- Collection> values = (Collection>) values1;
- mySubscriptionInterceptorList.addAll(values);
-
-
LinkedBlockingQueue executorQueue = new LinkedBlockingQueue<>(1000);
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
.namingPattern("SubscriptionTriggering-%d")
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java
new file mode 100644
index 00000000000..7c63a0c1e2a
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbcache/DaoSubscriptionProvider.java
@@ -0,0 +1,55 @@
+package ca.uhn.fhir.jpa.subscription.dbcache;
+
+/*-
+ * #%L
+ * HAPI FHIR JPA Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class DaoSubscriptionProvider implements ISubscriptionProvider {
+ @Autowired
+ DaoRegistry myDaoRegistry;
+ @Autowired
+ private SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor;
+
+ @Override
+ public IBundleProvider search(SearchParameterMap theMap) {
+ IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
+ RequestDetails req = new ServletSubRequestDetails();
+ req.setSubRequest(true);
+
+ return subscriptionDao.search(theMap, req);
+ }
+
+ @Override
+ public boolean loadSubscription(IBaseResource theResource) {
+ return mySubscriptionActivatingInterceptor.activateOrRegisterSubscriptionIfRequired(theResource);
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java
similarity index 52%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java
index 788aa9ef139..84549dd1dbd 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherCompositeInMemoryDatabase.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/CompositeInMemoryDaoSubscriptionMatcher.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.dbmatcher;
/*-
* #%L
@@ -21,34 +21,38 @@ package ca.uhn.fhir.jpa.subscription.matcher;
*/
import ca.uhn.fhir.jpa.dao.DaoConfig;
-import ca.uhn.fhir.jpa.subscription.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.SubscriptionMatchResult;
+import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-@Service
-public class SubscriptionMatcherCompositeInMemoryDatabase implements ISubscriptionMatcher {
- private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherCompositeInMemoryDatabase.class);
+public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMatcher {
+ private Logger ourLog = LoggerFactory.getLogger(CompositeInMemoryDaoSubscriptionMatcher.class);
- @Autowired
- SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
- @Autowired
- SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
+ private final DaoSubscriptionMatcher myDaoSubscriptionMatcher;
+ private final InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
@Autowired
DaoConfig myDaoConfig;
+ public CompositeInMemoryDaoSubscriptionMatcher(DaoSubscriptionMatcher theDaoSubscriptionMatcher, InMemorySubscriptionMatcher theInMemorySubscriptionMatcher) {
+ myDaoSubscriptionMatcher = theDaoSubscriptionMatcher;
+ myInMemorySubscriptionMatcher = theInMemorySubscriptionMatcher;
+ }
+
@Override
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
SubscriptionMatchResult result;
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
- result = mySubscriptionMatcherInMemory.match(criteria, msg);
+ result = myInMemorySubscriptionMatcher.match(criteria, msg);
if (!result.supported()) {
ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason());
- result = mySubscriptionMatcherDatabase.match(criteria, msg);
+ result = myDaoSubscriptionMatcher.match(criteria, msg);
}
} else {
- result = mySubscriptionMatcherDatabase.match(criteria, msg);
+ result = myDaoSubscriptionMatcher.match(criteria, msg);
}
return result;
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java
similarity index 87%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java
rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java
index 4614fde8207..6b1fbad53f0 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherDatabase.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/dbmatcher/DaoSubscriptionMatcher.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.dbmatcher;
/*-
* #%L
@@ -24,10 +24,12 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
-import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+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.SubscriptionMatchResult;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -35,13 +37,9 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Service;
-@Service
-@Lazy
-public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
- private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class);
+public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
+ private Logger ourLog = LoggerFactory.getLogger(DaoSubscriptionMatcher.class);
@Autowired
private FhirContext myCtx;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java
deleted file mode 100644
index 0e3c85db169..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionEmailInterceptor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package ca.uhn.fhir.jpa.subscription.email;
-
-/*-
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2018 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import org.apache.commons.lang3.Validate;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.messaging.MessageHandler;
-import org.springframework.stereotype.Component;
-
-import java.util.Optional;
-
-/**
- * Note: If you're going to use this, you need to provide a bean
- * of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
- * in your own Spring config
- */
-public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
-
- /**
- * This is set to autowired=false just so that implementors can supply this
- * with a mechanism other than autowiring if they want
- */
- @Autowired(required = false)
- private IEmailSender myEmailSender;
- @Autowired
- BeanFactory myBeanFactory;
- private String myDefaultFromAddress = "noreply@unknown.com";
-
- @Override
- protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) {
- return Optional.of(myBeanFactory.getBean(SubscriptionDeliveringEmailSubscriber.class, getChannelType(), this));
- }
-
- @Override
- public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
- return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.EMAIL;
- }
-
- /**
- * The "from" address to use for any sent emails that to not explicitly specity a from address
- */
- public String getDefaultFromAddress() {
- return myDefaultFromAddress;
- }
-
- /**
- * The "from" address to use for any sent emails that to not explicitly specity a from address
- */
- public void setDefaultFromAddress(String theDefaultFromAddress) {
- Validate.notBlank(theDefaultFromAddress, "theDefaultFromAddress must not be null or blank");
- myDefaultFromAddress = theDefaultFromAddress;
- }
-
- public IEmailSender getEmailSender() {
- return myEmailSender;
- }
-
- /**
- * Set the email sender (this method does not need to be explicitly called if you
- * are using autowiring to supply the sender)
- */
- public void setEmailSender(IEmailSender theEmailSender) {
- myEmailSender = theEmailSender;
- }
-
-
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD
deleted file mode 100644
index 22e4943bdad..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java~HEAD
+++ /dev/null
@@ -1,7 +0,0 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
-
-import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
-
-public interface ISubscriptionMatcher {
- boolean match(String criteria, ResourceModifiedMessage msg);
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java
deleted file mode 100644
index 06668501498..00000000000
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionRestHookInterceptor.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package ca.uhn.fhir.jpa.subscription.resthook;
-
-/*-
- * #%L
- * HAPI FHIR JPA Server
- * %%
- * Copyright (C) 2014 - 2018 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import org.springframework.beans.factory.BeanFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.MessageHandler;
-
-import java.util.Optional;
-
-public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
- @Autowired
- BeanFactory myBeanFactory;
-
- @Override
- protected Optional createDeliveryHandler(CanonicalSubscription theSubscription) {
- SubscriptionDeliveringRestHookSubscriber value = myBeanFactory.getBean(SubscriptionDeliveringRestHookSubscriber.class, getChannelType(), this);
- return Optional.of(value);
- }
-
- @Override
- public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
- return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
- }
-
-}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java
index f155a961edf..87a617aad51 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/JpaConstants.java
@@ -23,51 +23,6 @@ package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.rest.api.Constants;
public class JpaConstants {
-
- /**
- *
- * This extension should be of type string and should be
- * placed on the Subscription.channel element
- *
- */
- public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
-
- /**
- *
- * This extension should be of type string and should be
- * placed on the Subscription.channel element
- *
- */
- public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
-
-
- /**
- * This extension URL indicates whether a REST HOOK delivery should
- * include the version ID when delivering.
- *
- * This extension should be of type boolean and should be
- * placed on the Subscription.channel element.
- *
- */
- public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
-
- /**
- * This extension URL indicates whether a REST HOOK delivery should
- * reload the resource and deliver the latest version always. This
- * could be useful for example if a resource which triggers a
- * subscription gets updated many times in short succession and there
- * is no value in delivering the older versions.
- *
- * Note that if the resource is now deleted, this may cause
- * the delivery to be cancelled altogether.
- *
- *
- *
- * This extension should be of type boolean and should be
- * placed on the Subscription.channel element.
- *
"));
}
+ @Test
+ public void testTerminologyWithCompleteCs_Expand() throws Exception {
+
+ CodeSystem cs = new CodeSystem();
+ cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
+ cs.setUrl("http://cs");
+ CodeSystem.ConceptDefinitionComponent a = cs.addConcept()
+ .setCode("A");
+ a.addConcept().setCode("A1");
+ a.addConcept().setCode("A2");
+ CodeSystem.ConceptDefinitionComponent b = cs.addConcept()
+ .setCode("B");
+ b.addConcept().setCode("B1");
+ b.addConcept().setCode("B2");
+ ourClient.create().resource(cs).execute();
+
+ ValueSet vs = new ValueSet();
+ vs.setUrl("http://vs");
+ vs.getCompose()
+ .addInclude()
+ .setSystem("http://cs")
+ .addFilter()
+ .setProperty("concept")
+ .setOp(ValueSet.FilterOperator.ISA)
+ .setValue("A");
+ IIdType vsid = ourClient.create().resource(vs).execute().getId().toUnqualifiedVersionless();
+
+ HttpGet read = new HttpGet(ourServerBase + "/" + vsid.getValue() + "/$expand");
+ try (CloseableHttpResponse response = ourHttpClient.execute(read)) {
+ String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(text);
+ assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
+ assertThat(text, containsString("\"A\""));
+ assertThat(text, containsString("\"A1\""));
+ assertThat(text, not(containsString("\"B\"")));
+ assertThat(text, not(containsString("\"B1\"")));
+ }
+
+
+// HttpGet read = new HttpGet(ourServerBase + "/Observation?patient=P5000000302&_sort:desc=code&code:in=http://fkcfhir.org/fhir/vs/ccdacapddialysisorder");
+// try (CloseableHttpResponse response = ourHttpClient.execute(read)) {
+// String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+// ourLog.info(text);
+// assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
+// assertThat(text, not(containsString("\"text\",\"type\"")));
+// }
+ }
+
+ @Test
+ public void testTerminologyWithCompleteCs_SearchForConceptIn() throws Exception {
+
+ CodeSystem cs = new CodeSystem();
+ cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE);
+ cs.setUrl("http://cs");
+ CodeSystem.ConceptDefinitionComponent a = cs.addConcept()
+ .setCode("A");
+ a.addConcept().setCode("A1");
+ a.addConcept().setCode("A2");
+ CodeSystem.ConceptDefinitionComponent b = cs.addConcept()
+ .setCode("B");
+ b.addConcept().setCode("B1");
+ b.addConcept().setCode("B2");
+ ourClient.create().resource(cs).execute();
+
+ ValueSet vs = new ValueSet();
+ vs.setUrl("http://vs");
+ vs.getCompose()
+ .addInclude()
+ .setSystem("http://cs")
+ .addFilter()
+ .setProperty("concept")
+ .setOp(ValueSet.FilterOperator.ISA)
+ .setValue("A");
+ ourClient.create().resource(vs).execute().getId().toUnqualifiedVersionless();
+
+ Observation obs = new Observation();
+ obs.getCode().addCoding().setSystem("http://cs").setCode("A1");
+ obs.setValue(new StringType("OBS1"));
+ obs.setStatus(ObservationStatus.FINAL);
+ ourClient.create().resource(obs).execute();
+
+ Observation obs2 = new Observation();
+ obs2.getCode().addCoding().setSystem("http://cs").setCode("B1");
+ obs2.setStatus(ObservationStatus.FINAL);
+ obs2.setValue(new StringType("OBS2"));
+ ourClient.create().resource(obs2).execute();
+
+ HttpGet read = new HttpGet(ourServerBase + "/Observation?code:in=http://vs");
+ try (CloseableHttpResponse response = ourHttpClient.execute(read)) {
+ String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+ ourLog.info(text);
+ assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatusLine().getStatusCode());
+ assertThat(text, containsString("\"OBS1\""));
+ assertThat(text, not(containsString("\"OBS2\"")));
+ }
+
+
+ }
+
@Test
public void testSearchBundleDoesntIncludeTextElement() throws Exception {
HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json");
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java
index 1e7152417b6..a11ad70f804 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/search/reindex/ResourceReindexingSvcImplTest.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -63,6 +64,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
@Captor
private ArgumentCaptor myHighCaptor;
private ResourceReindexJobEntity mySingleJob;
+ @Mock
+ private ISearchParamRegistry mySearchParamRegistry;
@Override
protected FhirContext getContext() {
@@ -87,6 +90,7 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
mySvc.setReindexJobDaoForUnitTest(myReindexJobDao);
mySvc.setResourceTableDaoForUnitTest(myResourceTableDao);
mySvc.setTxManagerForUnitTest(myTxManager);
+ mySvc.setSearchParamRegistryForUnitTest(mySearchParamRegistry);
mySvc.start();
}
@@ -175,6 +179,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
verify(myReindexJobDao, times(1)).getReindexCount(any());
verify(myReindexJobDao, times(1)).setReindexCount(any(), anyInt());
verifyNoMoreInteractions(myReindexJobDao);
+
+ verify(mySearchParamRegistry, times(1)).forceRefresh();
}
@Test
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
similarity index 88%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
index 04f6a800449..0085e922499 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/BaseSubscriptionsR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionsR4Test.java
@@ -1,11 +1,9 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
-import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
+import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
@@ -26,7 +24,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.support.ExecutorSubscribableChannel;
+import org.springframework.messaging.SubscribableChannel;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
@@ -39,7 +37,6 @@ import java.util.List;
public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class);
-
private static int ourListenerPort;
private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer;
@@ -50,9 +47,9 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@Autowired
private SingleQueryCountHolder myCountHolder;
@Autowired
- protected DaoConfig myDaoConfig;
+ protected SubscriptionTestUtil mySubscriptionTestUtil;
@Autowired
- private DaoRegistry myDaoRegistry;
+ protected SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
protected CountingInterceptor myCountingInterceptor;
@@ -65,7 +62,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@After
public void afterUnregisterRestHookListener() {
- BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false);
+ SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false);
for (IIdType next : mySubscriptionIds) {
IIdType nextId = next.toUnqualifiedVersionless();
@@ -81,12 +78,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
- ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
@@ -101,12 +98,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
ourClient.delete().resource(next).execute();
}
- waitForRegisteredSubscriptionCount(0);
+ waitForActivatedSubscriptionCount(0);
- ExecutorSubscribableChannel processingChannel = (ExecutorSubscribableChannel) getRestHookSubscriptionInterceptor().getProcessingChannel();
- processingChannel.setInterceptors(new ArrayList<>());
+ SubscriptionChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
+ processingChannel.clearInterceptorsForUnitTest();
myCountingInterceptor = new CountingInterceptor();
- processingChannel.addInterceptor(myCountingInterceptor);
+ processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
}
@@ -135,7 +132,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
protected void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
@PostConstruct
@@ -156,8 +153,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
- String observationId = methodOutcome.getId().getIdPart();
- observation.setId(observationId);
+ observation.setId(methodOutcome.getId());
return observation;
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
similarity index 92%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
index 287c3c8b898..ab63d835282 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/CountingInterceptor.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/CountingInterceptor.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java
similarity index 84%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java
index f75f67152f5..6af748eda5d 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirClientSearchParamProviderTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProviderTest.java
@@ -1,10 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription;
-import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
-import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
-import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider;
+import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations;
@@ -20,18 +18,18 @@ import static org.junit.Assert.assertEquals;
public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
@Autowired
- BaseSearchParamRegistry mySearchParamRegistry;
+ ISearchParamRegistry mySearchParamRegistry;
@Autowired
ISearchParamProvider origSearchParamProvider;
@Before
public void useFhirClientSearchParamProvider() {
- mySearchParamRegistry.setSearchParamProvider(new FhirClientSearchParamProvider(ourClient));
+ mySearchParamRegistry.setSearchParamProviderForUnitTest(new FhirClientSearchParamProvider(ourClient));
}
@After
public void revert() {
- mySearchParamRegistry.setSearchParamProvider(origSearchParamProvider);
+ mySearchParamRegistry.setSearchParamProviderForUnitTest(origSearchParamProvider);
}
@Test
@@ -48,7 +46,7 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
createSubscription(criteria, "application/json");
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
{
Observation observation = new Observation();
@@ -81,8 +79,5 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
waitForQueueToDrain();
waitForSize(2, ourUpdatedObservations);
}
-
}
-
-
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java
new file mode 100644
index 00000000000..5d23b900dc1
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirClientSubscriptionProviderTest.java
@@ -0,0 +1,61 @@
+package ca.uhn.fhir.jpa.subscription;
+
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
+import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider;
+import ca.uhn.fhir.rest.api.Constants;
+import org.hl7.fhir.dstu3.model.Subscription;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class FhirClientSubscriptionProviderTest extends BaseSubscriptionsR4Test {
+ @Autowired
+ SubscriptionLoader mySubscriptionLoader;
+ @Autowired
+ ISubscriptionProvider origSubscriptionProvider;
+ @Autowired
+ AutowireCapableBeanFactory autowireCapableBeanFactory;
+
+ @Before
+ public void useFhirClientSubscriptionProvider() {
+ FhirClientSubscriptionProvider subscriptionProvider = new FhirClientSubscriptionProvider(ourClient);
+ // This bean is only available in the standalone subscription context, so we have to autowire it manually.
+ autowireCapableBeanFactory.autowireBean(subscriptionProvider);
+ mySubscriptionLoader.setSubscriptionProviderForUnitTest(subscriptionProvider);
+ }
+
+ @After
+ public void revert() {
+ mySubscriptionLoader.setSubscriptionProviderForUnitTest(origSubscriptionProvider);
+ }
+
+ private String myCode = "1000000050";
+
+ @Test
+ public void testSubscriptionLoaderFhirClient() throws Exception {
+ String payload = "application/fhir+json";
+
+ String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
+ String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml";
+
+
+ List subs = new ArrayList<>();
+ createSubscription(criteria1, payload);
+ createSubscription(criteria2, payload);
+ waitForActivatedSubscriptionCount(2);
+
+ sendObservation(myCode, "SNOMED-CT");
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(1, ourUpdatedObservations);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java
similarity index 93%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java
index a2b085d7174..9bad30dd6ae 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/FhirR4Util.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/FhirR4Util.java
@@ -1,10 +1,11 @@
-package ca.uhn.fhir.jpa.subscription.r4;
-
-import org.hl7.fhir.r4.model.*;
-import org.hl7.fhir.instance.model.api.*;
+package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient;
+import org.hl7.fhir.instance.model.api.IBaseCoding;
+import org.hl7.fhir.instance.model.api.IBaseMetaType;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.*;
public class FhirR4Util {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java
new file mode 100644
index 00000000000..f2347c48e87
--- /dev/null
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTestUtil.java
@@ -0,0 +1,86 @@
+package ca.uhn.fhir.jpa.subscription;
+
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel;
+import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber;
+import org.hl7.fhir.instance.model.Subscription;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class SubscriptionTestUtil {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionTestUtil.class);
+
+ private JavaMailEmailSender myEmailSender;
+
+ @Autowired
+ private DaoConfig myDaoConfig;
+ @Autowired
+ private SubscriptionInterceptorLoader mySubscriptionInterceptorLoader;
+ @Autowired
+ private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
+ @Autowired
+ private SubscriptionRegistry mySubscriptionRegistry;
+
+ public int getExecutorQueueSize() {
+ LinkedBlockingQueueSubscriptionChannel channel = (LinkedBlockingQueueSubscriptionChannel) mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
+ return channel.getQueueSizeForUnitTest();
+ }
+
+ // TODO KHS replace this and similar functions with CountdownLatch
+ public void waitForQueueToDrain() throws InterruptedException {
+ Thread.sleep(100);
+ ourLog.info("Executor work queue has {} items", getExecutorQueueSize());
+ if (getExecutorQueueSize() > 0) {
+ while (getExecutorQueueSize() > 0) {
+ Thread.sleep(50);
+ }
+ ourLog.info("Executor work queue has {} items", getExecutorQueueSize());
+ }
+ Thread.sleep(100);
+ }
+
+ public void registerEmailInterceptor() {
+ myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL);
+ mySubscriptionInterceptorLoader.registerInterceptors();
+ }
+
+ public void registerRestHookInterceptor() {
+ myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK);
+ mySubscriptionInterceptorLoader.registerInterceptors();
+ }
+
+ public void registerWebSocketInterceptor() {
+ myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET);
+ mySubscriptionInterceptorLoader.registerInterceptors();
+ }
+
+ public void unregisterSubscriptionInterceptor() {
+ myDaoConfig.clearSupportedSubscriptionTypesForUnitTest();
+ mySubscriptionInterceptorLoader.unregisterInterceptorsForUnitTest();
+ }
+
+ public int getExecutorQueueSizeForUnitTests() {
+ return getExecutorQueueSize();
+ }
+
+ public void initEmailSender(int theListenerPort) {
+ myEmailSender = new JavaMailEmailSender();
+ myEmailSender.setSmtpServerHostname("localhost");
+ myEmailSender.setSmtpServerPort(theListenerPort);
+ myEmailSender.start();
+ }
+
+ public void setEmailSender(IIdType theIdElement) {
+ ActiveSubscription activeSubscription = mySubscriptionRegistry.get(theIdElement.getIdPart());
+ SubscriptionDeliveringEmailSubscriber subscriber = (SubscriptionDeliveringEmailSubscriber) activeSubscription.getDeliveryHandlerForUnitTest();
+ subscriber.setEmailSender(myEmailSender);
+ }
+
+ public IEmailSender getEmailSender() {
+ return myEmailSender;
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java
similarity index 73%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java
index a722b4ac937..582ad7aaa57 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu2Test.java
@@ -1,10 +1,7 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.email;
-import ca.uhn.fhir.jpa.config.BaseConfig;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
-import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
-import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
@@ -22,8 +19,6 @@ import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.core.task.AsyncTaskExecutor;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
@@ -42,12 +37,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
private List mySubscriptionIds = new ArrayList<>();
@Autowired
- private SubscriptionEmailInterceptor mySubscriber;
- @Autowired
- private List> myResourceDaos;
- @Autowired
- @Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
- private AsyncTaskExecutor myAsyncTaskExecutor;
+ private SubscriptionTestUtil mySubscriptionTestUtil;
@After
public void after() throws Exception {
@@ -58,38 +48,16 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.delete().resourceById(next).execute();
}
mySubscriptionIds.clear();
-
- ourRestServer.unregisterInterceptor(mySubscriber);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void before() throws Exception {
super.before();
- JavaMailEmailSender emailSender = new JavaMailEmailSender();
- emailSender.setSmtpServerHostname("localhost");
- emailSender.setSmtpServerPort(ourListenerPort);
- emailSender.start();
+ mySubscriptionTestUtil.initEmailSender(ourListenerPort);
- mySubscriber.setEmailSender(emailSender);
- mySubscriber.setResourceDaos(myResourceDaos);
- mySubscriber.setFhirContext(myFhirCtx);
- mySubscriber.setTxManager(ourTxManager);
- mySubscriber.setAsyncTaskExecutorForUnitTest(myAsyncTaskExecutor);
- mySubscriber.start();
- ourRestServer.registerInterceptor(mySubscriber);
-
-// ourLog.info("Sending test email to warm up the server");
-// EmailDetails details = new EmailDetails();
-// details.setFrom("a@a.com");
-// details.setTo(Arrays.asList("b@b.com"));
-// details.setSubjectTemplate("SUBJ");
-// details.setBodyTemplate("BODY");
-// emailSender.send(details);
-// ourLog.info("Done sending test email to warm up the server");
-// Store store = ourTestSmtp.getManagers().getImapHostManager().getStore();
-// MailFolder mailbox = store.getMailbox(ImapConstants.USER_NAMESPACE);
-// mailbox.deleteAllMessages();
+ mySubscriptionTestUtil.registerEmailInterceptor();
}
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
@@ -108,7 +76,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
subscription.setId(methodOutcome.getId().getIdPart());
mySubscriptionIds.add(methodOutcome.getId());
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
return subscription;
}
@@ -138,8 +106,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload, "to1@example.com,to2@example.com");
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
-
+ mySubscriptionTestUtil.waitForQueueToDrain();
+ mySubscriptionTestUtil.setEmailSender(subscription1.getIdElement());
assertEquals(0, Arrays.asList(ourTestSmtp.getReceivedMessages()).size());
Observation observation1 = sendObservation(code, "SNOMED-CT");
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java
index 4035b3a9ed3..02705f1d770 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java
@@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.subscription.email;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
-import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
-import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists;
import com.icegreen.greenmail.store.FolderException;
@@ -13,6 +13,7 @@ import com.icegreen.greenmail.util.ServerSetup;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
@@ -21,7 +22,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
/**
* Test the rest-hook subscriptions
@@ -29,6 +30,10 @@ import static org.junit.Assert.*;
public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmailSubscriptionDstu3Test.class);
+
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
private static List ourCreatedObservations = Lists.newArrayList();
private static int ourListenerPort;
private static List ourContentTypes = new ArrayList<>();
@@ -51,23 +56,17 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(ourEmailSubscriptionInterceptor);
-
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterEmailListener() throws FolderException {
ourTestSmtp.purgeEmailFromAllMailboxes();
- ;
- ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerEmailInterceptor();
- JavaMailEmailSender emailSender = new JavaMailEmailSender();
- emailSender.setSmtpServerHostname("localhost");
- emailSender.setSmtpServerPort(ourListenerPort);
- emailSender.start();
+ mySubscriptionTestUtil.initEmailSender(ourListenerPort);
- ourEmailSubscriptionInterceptor.setEmailSender(emailSender);
- ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io");
+ myDaoConfig.setEmailFromAddress("123@hapifhir.io");
}
private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException {
@@ -115,8 +114,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
- createSubscription(criteria1, payload);
+ Subscription subscription = createSubscription(criteria1, payload);
waitForQueueToDrain();
+ mySubscriptionTestUtil.setEmailSender(subscription.getIdElement());
sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
@@ -150,19 +150,18 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension()
- .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
+ .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("mailto:myfrom@from.com"));
subscriptionTemp.getChannel().addExtension()
- .setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
+ .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
-
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp));
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();
-
+ mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
@@ -197,10 +196,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension()
- .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
+ .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("myfrom@from.com"));
subscriptionTemp.getChannel().addExtension()
- .setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
+ .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
@@ -208,6 +207,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Subscription ID is: {}", id.getValue());
waitForQueueToDrain();
+ mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
@@ -238,7 +238,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
@AfterClass
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java
index 828441acb29..6c6b8a6cd59 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSenderTest.java
@@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.subscription.email;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.EmailDetails;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.GreenMailUtil;
@@ -15,7 +17,7 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Arrays;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
public class JavaMailEmailSenderTest {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java
similarity index 98%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java
index 943987dfc5b..c4ece8f0207 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR4.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR4.java
@@ -1,10 +1,10 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*;
@@ -29,18 +29,18 @@ import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestR4Config.class})
-public class SubscriptionMatcherInMemoryTestR4 {
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionMatcherInMemoryTestR4.class);
+public class InMemorySubscriptionMatcherTestR4 {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InMemorySubscriptionMatcherTestR4.class);
@Autowired
- SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
+ InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
@Autowired
FhirContext myContext;
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
String criteria = params.toNormalizedQueryString(myContext);
ourLog.info("Criteria: <{}>", criteria);
- return mySubscriptionMatcherInMemory.match(criteria, resource);
+ return myInMemorySubscriptionMatcher.match(criteria, resource);
}
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
@@ -380,18 +380,16 @@ public class SubscriptionMatcherInMemoryTestR4 {
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam"));
try {
String criteria = params.toNormalizedQueryString(myContext);
- ResourceModifiedMessage msg = new ResourceModifiedMessage();
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE);
msg.setSubscriptionId("Subscription/123");
msg.setId(new IdType("Patient/ABC"));
- msg.setNewPayload(myContext, patient);
- SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, msg);
+ SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, msg);
fail();
} catch (InternalErrorException e){
assertEquals("Failure processing resource ID[Patient/ABC] for subscription ID[Subscription/123]: Invalid resource reference found at path[Patient.managingOrganization] - Does not contain resource type - urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage());
}
}
-
@Test
public void testSearchResourceReferenceOnlyCorrectPath() {
Organization org = new Organization();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java
similarity index 87%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java
index 8d0c3a44baa..8d34fd1f8c2 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookActivatesPreExistingSubscriptionsR4Test.java
@@ -1,9 +1,9 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
-import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
-import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber;
+import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.Constants;
@@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.*;
import org.junit.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
@@ -28,7 +29,6 @@ import java.util.Enumeration;
import java.util.List;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test {
@@ -41,20 +41,23 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
private static List ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private static List ourHeaders = Collections.synchronizedList(new ArrayList<>());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void afterResetSubscriptionActivatingInterceptor() {
- SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
+ SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
}
@After
public void afterUnregisterRestHookListener() {
- ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeSetSubscriptionActivatingInterceptor() {
- SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
- getRestHookSubscriptionInterceptor().initSubscriptions();
+ SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
+ mySubscriptionLoader.initSubscriptions();
}
@@ -105,11 +108,8 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
createSubscription(criteria1, payload, ourListenerServerBase);
createSubscription(criteria2, payload, ourListenerServerBase);
- ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
- getRestHookSubscriptionInterceptor().initSubscriptions();
-
- assertTrue(hasRestHookSubscriptionInterceptor());
-
+ mySubscriptionTestUtil.registerRestHookInterceptor();
+ mySubscriptionLoader.initSubscriptions();
sendObservation(code, "SNOMED-CT");
@@ -120,9 +120,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
}
private void waitForQueueToDrain() throws InterruptedException {
- if (hasRestHookSubscriptionInterceptor()) {
- RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
- }
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
public static class ObservationListener implements IResourceProvider {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java
similarity index 93%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java
index bfe49f57518..2a79125a84e 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu2Test.java
@@ -1,8 +1,9 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
@@ -27,11 +28,13 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
/**
* Test the rest-hook subscriptions
@@ -47,6 +50,9 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
private static List ourUpdatedObservations = Lists.newArrayList();
private List mySubscriptionIds = new ArrayList();
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void afterUnregisterRestHookListener() {
ourLog.info("** AFTER **");
@@ -62,12 +68,12 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
- ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
@@ -279,7 +285,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
@BeforeClass
@@ -309,18 +315,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourListenerServer.stop();
}
- public static void waitForQueueToDrain(BaseSubscriptionInterceptor theSubscriptionInterceptor) throws InterruptedException {
- Thread.sleep(100);
- ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests());
- if (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) {
- while (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) {
- Thread.sleep(50);
- }
- ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests());
- }
- Thread.sleep(100);
- }
-
public static class ObservationListener implements IResourceProvider {
@Create
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java
similarity index 97%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java
index fcaa6964165..97716aac54f 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestDstu3Test.java
@@ -1,9 +1,10 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
@@ -21,6 +22,7 @@ import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
@@ -45,6 +47,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
private static List ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void afterUnregisterRestHookListener() {
ourLog.info("**** Starting @After *****");
@@ -61,13 +66,12 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
-
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
- ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
@@ -346,7 +350,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
@BeforeClass
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
similarity index 78%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
index 81ec08ec3c7..b6ae76bc815 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestR4Test.java
@@ -1,42 +1,26 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription.resthook;
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.dao.DaoConfig;
-import ca.uhn.fhir.jpa.dao.DaoRegistry;
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
-import ca.uhn.fhir.jpa.util.JpaConstants;
-import ca.uhn.fhir.rest.annotation.Create;
-import ca.uhn.fhir.rest.annotation.ResourceParam;
-import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
+import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
import ca.uhn.fhir.rest.api.CacheControlDirective;
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.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
-import ca.uhn.fhir.util.BundleUtil;
-import ca.uhn.fhir.util.PortUtil;
-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.IBaseBundle;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*;
-import org.junit.*;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.messaging.support.ExecutorSubscribableChannel;
-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;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
@@ -46,7 +30,16 @@ import static org.junit.Assert.*;
* Test the rest-hook subscriptions
*/
public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestR4Test.class);
+ private static final Logger ourLog = LoggerFactory.getLogger(RestHookTestR4Test.class);
+
+ @Autowired
+ StoppableSubscriptionDeliveringRestHookSubscriber myStoppableSubscriptionDeliveringRestHookSubscriber;
+
+ @After
+ public void cleanupStoppableSubscriptionDeliveringRestHookSubscriber() {
+ myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(null);
+ myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
+ }
@Test
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
@@ -58,7 +51,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
createSubscription(criteria1, payload);
createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
sendObservation(code, "SNOMED-CT");
@@ -75,10 +68,10 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String payload = "application/fhir+json";
createSubscription(criteria, payload);
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
for (int i = 0; i < 5; i++) {
- Integer changes = ourReskHookSubscriptionInterceptor.doInitSubscriptions();
- assertEquals(0, changes.intValue());
+ int changes = this.mySubscriptionLoader.doInitSubscriptionsForUnitTest();
+ assertEquals(0, changes);
}
}
@@ -92,7 +85,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
createSubscription(criteria1, payload);
createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
Observation obs = sendObservation(code, "SNOMED-CT");
@@ -122,32 +115,137 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
- waitForRegisteredSubscriptionCount(0);
+ waitForActivatedSubscriptionCount(0);
Subscription subscription1 = createSubscription(criteria1, payload);
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
- int modCount = myCountingInterceptor.getSentCount();
- subscription1
- .getChannel()
- .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
- subscription1
- .getChannel()
- .addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
- ourLog.info("** About to update subscription");
- ourClient.update().resource(subscription1).execute();
- waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
ourLog.info("** About to send observation");
Observation observation1 = sendObservation(code, "SNOMED-CT");
- // Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
- assertEquals(observation1.getIdElement().getIdPart(), ourUpdatedObservations.get(0).getIdElement().getIdPart());
- assertEquals(null, ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
+ IdType idElement = ourUpdatedObservations.get(0).getIdElement();
+ assertEquals(observation1.getIdElement().getIdPart(), idElement.getIdPart());
+ // VersionId is present
+ assertEquals(observation1.getIdElement().getVersionIdPart(), idElement.getVersionIdPart());
+
+ subscription1
+ .getChannel()
+ .addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
+ ourLog.info("** About to update subscription");
+
+ int modCount = myCountingInterceptor.getSentCount();
+ ourClient.update().resource(subscription1).execute();
+ waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
+
+ ourLog.info("** About to send observation");
+ Observation observation2 = sendObservation(code, "SNOMED-CT");
+
+ waitForQueueToDrain();
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(2, ourUpdatedObservations);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(1));
+
+ idElement = ourUpdatedObservations.get(1).getIdElement();
+ assertEquals(observation2.getIdElement().getIdPart(), idElement.getIdPart());
+ // Now VersionId is stripped
+ assertEquals(null, idElement.getVersionIdPart());
+ }
+
+ @Test
+ public void testRestHookSubscriptionDoesntGetLatestVersionByDefault() throws Exception {
+ String payload = "application/json";
+
+ String code = "1000000050";
+ String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
+
+ waitForActivatedSubscriptionCount(0);
+ createSubscription(criteria1, payload);
+ waitForActivatedSubscriptionCount(1);
+
+ myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
+
+ ourLog.info("** About to send observation");
+ Observation observation = sendObservation(code, "SNOMED-CT");
+ assertEquals("1", observation.getIdElement().getVersionIdPart());
+ assertNull(observation.getComment());
+
+ observation.setComment("changed");
+ MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
+ assertEquals("2", methodOutcome.getId().getVersionIdPart());
+ assertEquals("changed", observation.getComment());
+
+ // Wait for our two delivery channel threads to be paused
+ assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
+ // Open the floodgates!
+ myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
+
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(2, ourUpdatedObservations);
+
+ Observation observation1 = ourUpdatedObservations.get(0);
+ Observation observation2 = ourUpdatedObservations.get(1);
+
+ assertEquals("1", observation1.getIdElement().getVersionIdPart());
+ assertNull(observation1.getComment());
+ assertEquals("2", observation2.getIdElement().getVersionIdPart());
+ assertEquals("changed", observation2.getComment());
+ }
+
+ @Test
+ public void testRestHookSubscriptionGetsLatestVersionWithFlag() throws Exception {
+ String payload = "application/json";
+
+ String code = "1000000050";
+ String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
+
+ waitForActivatedSubscriptionCount(0);
+
+ Subscription subscription = newSubscription(criteria1, payload);
+ subscription
+ .getChannel()
+ .addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
+ ourClient.create().resource(subscription).execute();
+
+ waitForActivatedSubscriptionCount(1);
+
+ myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+ myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
+
+ ourLog.info("** About to send observation");
+ Observation observation = sendObservation(code, "SNOMED-CT");
+ assertEquals("1", observation.getIdElement().getVersionIdPart());
+ assertNull(observation.getComment());
+
+ observation.setComment("changed");
+ MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
+ assertEquals("2", methodOutcome.getId().getVersionIdPart());
+ assertEquals("changed", observation.getComment());
+
+ // Wait for our two delivery channel threads to be paused
+ assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
+ // Open the floodgates!
+ myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
+
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(2, ourUpdatedObservations);
+
+ Observation observation1 = ourUpdatedObservations.get(0);
+ Observation observation2 = ourUpdatedObservations.get(1);
+
+ assertEquals("2", observation1.getIdElement().getVersionIdPart());
+ assertEquals("changed", observation1.getComment());
+ assertEquals("2", observation2.getIdElement().getVersionIdPart());
+ assertEquals("changed", observation2.getComment());
}
@Test
@@ -160,7 +258,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
@@ -240,7 +338,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
@@ -318,7 +416,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
ourLog.info("** About to send obervation");
Observation observation1 = sendObservation(code, "SNOMED-CT");
@@ -384,7 +482,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
@Test
public void testSubscriptionTriggerViaSubscription() throws Exception {
- BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true);
+ SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true);
String payload = "application/xml";
@@ -392,7 +490,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
createSubscription(criteria1, payload);
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
ourLog.info("** About to send obervation");
@@ -490,7 +588,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
- waitForRegisteredSubscriptionCount(2);
+ waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
@@ -526,7 +624,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
// Add some headers, and we'll also turn back to requested status for fun
Subscription subscription = createSubscription(criteria1, payload);
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
subscription.getChannel().addHeader("X-Foo: FOO");
subscription.getChannel().addHeader("X-Bar: BAR");
@@ -553,7 +651,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscription = createSubscription(criteria1, payload);
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
sendObservation(code, "SNOMED-CT");
@@ -644,7 +742,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh();
createSubscription(criteria, "application/json");
- waitForRegisteredSubscriptionCount(1);
+ waitForActivatedSubscriptionCount(1);
{
Observation bodySite = new Observation();
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java
similarity index 96%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java
index c542bb0f9ab..599ee55a986 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test.java
@@ -1,8 +1,9 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Observation;
@@ -25,6 +26,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections;
import java.util.List;
@@ -42,6 +44,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
private static String ourListenerServerBase;
private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void afterUnregisterRestHookListener() {
ourLog.info("** AFTER **");
@@ -51,12 +56,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
- myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
@@ -64,11 +69,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourCreatedObservations.clear();
ourUpdatedObservations.clear();
- ourRestHookSubscriptionInterceptor.initSubscriptions();
+ mySubscriptionLoader.initSubscriptions();
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java
similarity index 93%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java
index 5064960ee91..889a723d4eb 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.java
@@ -1,29 +1,30 @@
-package ca.uhn.fhir.jpa.subscription;
-
-import static org.junit.Assert.*;
-
-import java.util.Collections;
-import java.util.List;
+package ca.uhn.fhir.jpa.subscription.resthook;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
+import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import com.google.common.collect.Lists;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
-import com.google.common.collect.Lists;
-
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.dao.DaoConfig;
-import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
-import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
-import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.annotation.*;
-import ca.uhn.fhir.rest.api.MethodOutcome;
-import ca.uhn.fhir.rest.server.IResourceProvider;
-import ca.uhn.fhir.rest.server.RestfulServer;
+import java.util.Collections;
+import java.util.List;
/**
* Test the rest-hook subscriptions
@@ -38,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.class);
private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@Override
protected boolean shouldLogClient() {
return false;
@@ -50,21 +54,21 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute();
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
-
- myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor);
- SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
+
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
+ SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
}
@Before
public void beforeRegisterRestHookListener() {
- myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
public void beforeReset() {
ourCreatedObservations.clear();
ourUpdatedObservations.clear();
- SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
+ SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
}
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
@@ -87,7 +91,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
private Observation sendObservation(String code, String system) throws InterruptedException {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java
similarity index 93%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java
index d1f95a93a7d..56782fbabef 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.java
@@ -1,28 +1,30 @@
-package ca.uhn.fhir.jpa.subscription.r4;
-
-import java.util.Collections;
-import java.util.List;
-
-import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.servlet.ServletContextHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
-import org.hl7.fhir.r4.model.*;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.junit.*;
-
-import com.google.common.collect.Lists;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
+import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
-import ca.uhn.fhir.rest.annotation.*;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
+import com.google.common.collect.Lists;
+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.r4.model.*;
+import org.junit.*;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Collections;
+import java.util.List;
/**
* Test the rest-hook subscriptions
@@ -37,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class);
private static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@Override
protected boolean shouldLogClient() {
return false;
@@ -49,13 +54,13 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute();
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
-
- myDaoConfig.getInterceptors().remove(getRestHookSubscriptionInterceptor());
+
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
- myDaoConfig.getInterceptors().add(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
@@ -84,11 +89,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
}
private void waitForQueueToDrain() throws InterruptedException {
- ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests());
- while (getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests() > 0) {
+ ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests());
+ while (mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests() > 0) {
Thread.sleep(250);
}
- ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests());
+ ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests());
}
private Observation sendObservation(String code, String system) throws InterruptedException {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java
similarity index 90%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java
index 8e95fa6506f..dc23c1cf23e 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookWithEventDefinitionR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/RestHookWithEventDefinitionR4Test.java
@@ -1,7 +1,9 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
+import ca.uhn.fhir.jpa.subscription.FhirR4Util;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType;
@@ -11,6 +13,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.Collections;
@@ -44,6 +47,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
private String mySubscriptionId;
private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@Override
@After
public void after() throws Exception {
@@ -64,7 +70,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Override
@@ -76,6 +82,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
}
+ /**
+ * Ignored because this feature isn't implemented yet
+ */
@Test
@Ignore
public void testSubscriptionAddedTrigger() {
@@ -122,7 +131,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
@Before
public void beforeRegisterRestHookListener() {
- ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java
similarity index 97%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java
index e36eace4790..e1efd7e5659 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/SubscriptionTriggeringDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionTriggeringDstu3Test.java
@@ -1,9 +1,11 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
@@ -50,6 +52,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
private static List ourContentTypes = new ArrayList<>();
private List mySubscriptionIds = new ArrayList<>();
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void afterUnregisterRestHookListener() {
ourLog.info("**** Starting @After *****");
@@ -66,7 +71,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
- ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
mySubscriptionTriggeringSvc.cancelAll();
mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null);
@@ -79,7 +84,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
@Before
public void beforeRegisterRestHookListener() {
- ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.registerRestHookInterceptor();
}
/**
@@ -376,7 +381,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
}
private void waitForQueueToDrain() throws InterruptedException {
- RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
+ mySubscriptionTestUtil.waitForQueueToDrain();
}
public static class ObservationListener implements IResourceProvider {
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java
similarity index 96%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java
index 492398b6fab..54e1f616fff 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu2Test.java
@@ -1,6 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
+import ca.uhn.fhir.jpa.subscription.FhirDstu2Util;
+import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
@@ -27,7 +29,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
// This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java
similarity index 96%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java
index 9d90b49b931..b213c167654 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithCriteriaDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaDstu3Test.java
@@ -1,6 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
+import ca.uhn.fhir.jpa.subscription.FhirDstu3Util;
+import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session;
@@ -18,7 +20,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
// This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java
similarity index 97%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java
index dfe9b283c9f..ad34b30e238 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithCriteriaR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithCriteriaR4Test.java
@@ -1,6 +1,7 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
+import ca.uhn.fhir.jpa.subscription.FhirR4Util;
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
@@ -19,7 +20,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
// This is currently disabled as the criteria mechanism was a non-standard experiment
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java
similarity index 92%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java
index 7ba7a7d8366..4997528a8e5 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu2Test.java
@@ -1,7 +1,9 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
+import ca.uhn.fhir.jpa.subscription.FhirDstu2Util;
+import ca.uhn.fhir.jpa.subscription.SocketImplementation;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
@@ -21,13 +23,14 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
@@ -52,11 +55,13 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs
private WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation;
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void after() throws Exception {
super.after();
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.unregisterInterceptor(interceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@After
@@ -69,8 +74,7 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs
public void before() throws Exception {
super.before();
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.registerInterceptor(interceptor);
+ mySubscriptionTestUtil.registerWebSocketInterceptor();
/*
* Create patient
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java
similarity index 91%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java
index 6ce01a57b2c..5ac36a04baa 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/WebsocketWithSubscriptionIdDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdDstu3Test.java
@@ -1,7 +1,9 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
+import ca.uhn.fhir.jpa.subscription.FhirDstu3Util;
+import ca.uhn.fhir.jpa.subscription.SocketImplementation;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session;
@@ -12,13 +14,14 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
@@ -46,12 +49,14 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
private WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation;
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@After
public void after() throws Exception {
super.after();
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.unregisterInterceptor(interceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@After
@@ -67,8 +72,7 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0L);
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.registerInterceptor(interceptor);
+ mySubscriptionTestUtil.registerWebSocketInterceptor();
/*
* Create patient
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java
similarity index 91%
rename from hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java
rename to hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java
index a815ff10f58..374b72efa37 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/WebsocketWithSubscriptionIdR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/websocket/WebsocketWithSubscriptionIdR4Test.java
@@ -1,8 +1,9 @@
-package ca.uhn.fhir.jpa.subscription.r4;
+package ca.uhn.fhir.jpa.subscription.websocket;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
+import ca.uhn.fhir.jpa.subscription.FhirR4Util;
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
+import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session;
@@ -13,13 +14,14 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
/**
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
@@ -47,12 +49,14 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes
private WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation;
+ @Autowired
+ private SubscriptionTestUtil mySubscriptionTestUtil;
+
@Override
@After
public void after() throws Exception {
super.after();
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.unregisterInterceptor(interceptor);
+ mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@After
@@ -66,8 +70,7 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes
public void before() throws Exception {
super.before();
- SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
- ourRestServer.registerInterceptor(interceptor);
+ mySubscriptionTestUtil.registerWebSocketInterceptor();
/*
* Create patient
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java
index cbaa7c6fbd2..e7006342458 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/term/TerminologySvcImplDstu3Test.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.entity.TermConceptParentChildLink.RelationshipTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
+import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
@@ -23,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
@@ -583,6 +585,50 @@ public class TerminologySvcImplDstu3Test extends BaseJpaDstu3Test {
}
}
+ /**
+ * Check that a custom ValueSet against a custom CodeSystem expands correctly
+ */
+ @Test
+ public void testCustomValueSetExpansion() {
+
+ CodeSystem cs= new CodeSystem();
+ cs.setUrl("http://codesystems-r-us");
+ cs.setContent(CodeSystem.CodeSystemContentMode.NOTPRESENT);
+ IIdType csId = myCodeSystemDao.create(cs).getId().toUnqualifiedVersionless();
+
+ TermCodeSystemVersion version = new TermCodeSystemVersion();
+ version.getConcepts().add(new TermConcept(version, "A"));
+ version.getConcepts().add(new TermConcept(version, "B"));
+ version.getConcepts().add(new TermConcept(version, "C"));
+ version.getConcepts().add(new TermConcept(version, "D"));
+ runInTransaction(()->{
+ ResourceTable resTable = myEntityManager.find(ResourceTable.class, csId.getIdPartAsLong());
+ version.setResource(resTable);
+ myTermSvc.storeNewCodeSystemVersion(csId.getIdPartAsLong(), cs.getUrl(), "My System", version);
+ });
+
+ org.hl7.fhir.dstu3.model.ValueSet vs = new org.hl7.fhir.dstu3.model.ValueSet();
+ vs.setUrl("http://valuesets-r-us");
+ vs.getCompose()
+ .addInclude()
+ .setSystem(cs.getUrl())
+ .addConcept(new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode("A"))
+ .addConcept(new org.hl7.fhir.dstu3.model.ValueSet.ConceptReferenceComponent().setCode("C"));
+ myValueSetDao.create(vs);
+
+ org.hl7.fhir.dstu3.model.ValueSet expansion = myValueSetDao.expandByIdentifier(vs.getUrl(), null);
+ List expansionCodes = expansion
+ .getExpansion()
+ .getContains()
+ .stream()
+ .map(t -> t.getCode())
+ .sorted()
+ .collect(Collectors.toList());
+ assertEquals(Lists.newArrayList("A","C"), expansionCodes);
+
+ }
+
+
public static List toCodesContains(List theContains) {
List retVal = new ArrayList<>();
diff --git a/hapi-fhir-jpaserver-example/README.md b/hapi-fhir-jpaserver-example/README.md
index 8339170e048..5c37386c841 100644
--- a/hapi-fhir-jpaserver-example/README.md
+++ b/hapi-fhir-jpaserver-example/README.md
@@ -1,4 +1,18 @@
-## Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ
+# Unsupported
+
+This [hapi-fhir-jpaserver-example](https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jpaserver-example) project is no longer supported.
+
+
+## Supported JPA Example
+
+The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter)
+project within the [hapifhir](https://github.com/hapifhir) GitHub Organization.
+
+## Previous Documentation
+
+Below is the original documentation for this project. Note that this documentation is no longer being updated.
+
+#### Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ
Install Tomcat.
@@ -33,7 +47,7 @@ Point your browser (or fiddler, or what have you) to `http://localhost:8080/hapi
You should get an empty bundle back.
-## Running hapi-fhir-jpaserver-example in a Docker container
+#### Running hapi-fhir-jpaserver-example in a Docker container
Execute the `build-docker-image.sh` script to build the docker image.
diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
index 5a2e5fad948..38506ddd976 100644
--- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
+++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemo.java
@@ -1,23 +1,6 @@
package ca.uhn.fhir.jpa.demo;
-import java.util.Collection;
-import java.util.List;
-
-import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
-import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
-import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
-import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.servlet.ServletException;
-
-import org.hl7.fhir.dstu3.model.Bundle;
-import org.hl7.fhir.dstu3.model.Meta;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.WebApplicationContext;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
@@ -26,12 +9,22 @@ import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
+import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
-import ca.uhn.fhir.rest.server.*;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Meta;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.ServletException;
+import java.util.List;
public class JpaServerDemo extends RestfulServer {
@@ -134,12 +127,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
+ * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
*/
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
/*
* If you are hosting this server at a specific DNS name, the server will try to
diff --git a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
index 3147a776819..3c802e6cb10 100644
--- a/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
+++ b/hapi-fhir-jpaserver-example/src/main/java/ca/uhn/fhir/jpa/demo/JpaServerDemoDstu2.java
@@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo;
-import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.ServletException;
-
-import org.hl7.fhir.dstu3.model.Bundle;
-import org.hl7.fhir.dstu3.model.Meta;
-import org.springframework.web.context.ContextLoaderListener;
-import org.springframework.web.context.WebApplicationContext;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig;
@@ -20,12 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
-import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
-import ca.uhn.fhir.rest.server.*;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
+import ca.uhn.fhir.rest.server.ETagSupportEnum;
+import ca.uhn.fhir.rest.server.IResourceProvider;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import org.hl7.fhir.dstu3.model.Bundle;
+import org.hl7.fhir.dstu3.model.Meta;
+import org.springframework.web.context.ContextLoaderListener;
+import org.springframework.web.context.WebApplicationContext;
+
+import javax.servlet.ServletException;
+import java.util.List;
public class JpaServerDemoDstu2 extends RestfulServer {
@@ -128,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
/*
- * Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
+ * Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
*/
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
+ SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
+ subscriptionInterceptorLoader.registerInterceptors();
/*
* If you are hosting this server at a specific DNS name, the server will try to
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
index 7cbb219d533..c8b659e1da3 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/JdbcUtils.java
@@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.migrate;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,10 +22,19 @@ package ca.uhn.fhir.jpa.migrate;
import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver;
+import org.hibernate.engine.jdbc.env.internal.NormalizingIdentifierHelperImpl;
+import org.hibernate.engine.jdbc.env.spi.*;
+import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
+import org.hibernate.engine.jdbc.spi.TypeInfo;
+import org.hibernate.service.ServiceRegistry;
+import org.hibernate.tool.schema.extract.spi.ExtractionContext;
+import org.hibernate.tool.schema.extract.spi.SequenceInformation;
+import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ColumnMapRowMapper;
@@ -53,7 +62,7 @@ public class JdbcUtils {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
- ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), theTableName, false, true);
+ ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), false, true);
Set indexNames = new HashSet<>();
while (indexes.next()) {
@@ -81,7 +90,7 @@ public class JdbcUtils {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
- ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), theTableName, false, false);
+ ResultSet indexes = metadata.getIndexInfo(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), false, false);
while (indexes.next()) {
String indexName = indexes.getString("INDEX_NAME");
@@ -112,7 +121,7 @@ public class JdbcUtils {
metadata = connection.getMetaData();
String catalog = connection.getCatalog();
String schema = connection.getSchema();
- ResultSet indexes = metadata.getColumns(catalog, schema, theTableName, null);
+ ResultSet indexes = metadata.getColumns(catalog, schema, massageIdentifier(metadata, theTableName), null);
while (indexes.next()) {
@@ -165,7 +174,7 @@ public class JdbcUtils {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
- ResultSet indexes = metadata.getCrossReference(connection.getCatalog(), connection.getSchema(), theTableName, connection.getCatalog(), connection.getSchema(), theForeignTable);
+ ResultSet indexes = metadata.getCrossReference(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theForeignTable));
Set columnNames = new HashSet<>();
while (indexes.next()) {
@@ -201,7 +210,7 @@ public class JdbcUtils {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
- ResultSet indexes = metadata.getColumns(connection.getCatalog(), connection.getSchema(), theTableName, null);
+ ResultSet indexes = metadata.getColumns(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), null);
Set columnNames = new HashSet<>();
while (indexes.next()) {
@@ -233,27 +242,83 @@ public class JdbcUtils {
Set sequenceNames = new HashSet<>();
if (dialect.supportsSequences()) {
- String sql = dialect.getQuerySequencesString();
- if (sql != null) {
- Statement statement = null;
- ResultSet rs = null;
- try {
- statement = connection.createStatement();
- rs = statement.executeQuery(sql);
-
- while (rs.next()) {
- sequenceNames.add(rs.getString(1).toUpperCase());
- }
- } finally {
- if (rs != null) rs.close();
- if (statement != null) statement.close();
+ // Use Hibernate to get a list of current sequences
+ SequenceInformationExtractor sequenceInformationExtractor = dialect.getSequenceInformationExtractor();
+ ExtractionContext extractionContext = new ExtractionContext.EmptyExtractionContext() {
+ @Override
+ public Connection getJdbcConnection() {
+ return connection;
}
+ @Override
+ public ServiceRegistry getServiceRegistry() {
+ return super.getServiceRegistry();
+ }
+
+ @Override
+ public JdbcEnvironment getJdbcEnvironment() {
+ return new JdbcEnvironment() {
+ @Override
+ public Dialect getDialect() {
+ return dialect;
+ }
+
+ @Override
+ public ExtractedDatabaseMetaData getExtractedDatabaseMetaData() {
+ return null;
+ }
+
+ @Override
+ public Identifier getCurrentCatalog() {
+ return null;
+ }
+
+ @Override
+ public Identifier getCurrentSchema() {
+ return null;
+ }
+
+ @Override
+ public QualifiedObjectNameFormatter getQualifiedObjectNameFormatter() {
+ return null;
+ }
+
+ @Override
+ public IdentifierHelper getIdentifierHelper() {
+ return new NormalizingIdentifierHelperImpl(this, null, true, true, true, null, null, null);
+ }
+
+ @Override
+ public NameQualifierSupport getNameQualifierSupport() {
+ return null;
+ }
+
+ @Override
+ public SqlExceptionHelper getSqlExceptionHelper() {
+ return null;
+ }
+
+ @Override
+ public LobCreatorBuilder getLobCreatorBuilder() {
+ return null;
+ }
+
+ @Override
+ public TypeInfo getTypeInfoForJdbcCode(int jdbcTypeCode) {
+ return null;
+ }
+ };
+ }
+ };
+ Iterable sequences = sequenceInformationExtractor.extractMetadata(extractionContext);
+ for (SequenceInformation next : sequences) {
+ sequenceNames.add(next.getSequenceName().getSequenceName().getText());
}
+
}
return sequenceNames;
- } catch (SQLException e ) {
+ } catch (SQLException e) {
throw new InternalErrorException(e);
}
});
@@ -298,7 +363,7 @@ public class JdbcUtils {
DatabaseMetaData metadata;
try {
metadata = connection.getMetaData();
- ResultSet tables = metadata.getColumns(connection.getCatalog(), connection.getSchema(), theTableName, theColumnName);
+ ResultSet tables = metadata.getColumns(connection.getCatalog(), connection.getSchema(), massageIdentifier(metadata, theTableName), null);
while (tables.next()) {
String tableName = toUpperCase(tables.getString("TABLE_NAME"), Locale.US);
@@ -325,4 +390,14 @@ public class JdbcUtils {
});
}
}
+
+ private static String massageIdentifier(DatabaseMetaData theMetadata, String theCatalog) throws SQLException {
+ String retVal = theCatalog;
+ if (theMetadata.storesLowerCaseIdentifiers()) {
+ retVal = retVal.toLowerCase();
+ } else {
+ retVal = retVal.toUpperCase();
+ }
+ return retVal;
+ }
}
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java
index a863ac73d17..f3558eed294 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTableColumnTypeTask.java
@@ -66,6 +66,13 @@ public abstract class BaseTableColumnTypeTask extends B
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MSSQL_2012, "datetime2");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.ORACLE_12C, "timestamp");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.POSTGRES_9_4, "timestamp");
+
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.DERBY_EMBEDDED, "boolean");
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.MSSQL_2012, "bit");
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.MARIADB_10_1, "bit");
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.ORACLE_12C, "number(1,0)");
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.POSTGRES_9_4, "boolean");
+ setColumnType(ColumnTypeEnum.BOOLEAN, DriverTypeEnum.MYSQL_5_7, "bit");
}
public ColumnTypeEnum getColumnType() {
@@ -157,6 +164,13 @@ public abstract class BaseTableColumnTypeTask extends B
return "timestamp";
}
},
+ BOOLEAN {
+ @Override
+ public String getDescriptor(Long theColumnLength) {
+ Assert.isTrue(theColumnLength == null, "Must not supply a column length");
+ return "boolean";
+ }
+ },
INT {
@Override
public String getDescriptor(Long theColumnLength) {
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
index 63534dad81d..63d60abd89e 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java
@@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.model.entity;
* #L%
*/
+import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
+import org.hl7.fhir.instance.model.Subscription;
import java.util.Arrays;
import java.util.Collections;
@@ -53,6 +55,8 @@ public class ModelConfig {
private Set myTreatBaseUrlsAsLocal = new HashSet<>();
private Set myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
private boolean myDefaultSearchParamsCanBeOverridden = false;
+ private Set mySupportedSubscriptionTypes = new HashSet<>();
+ private String myEmailFromAddress = "noreply@unknown.com";
/**
* If set to {@code true} the default search params (i.e. the search parameters that are
@@ -297,6 +301,46 @@ public class ModelConfig {
return this;
}
+ /**
+ * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
+ * to the server matching these types will be activated.
+ *
+ */
+ public ModelConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
+ mySupportedSubscriptionTypes.add(theSubscriptionChannelType);
+ return this;
+ }
+
+ /**
+ * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
+ * to the server matching these types will be activated.
+ *
+ */
+ public Set getSupportedSubscriptionTypes() {
+ return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
+ }
+
+ @VisibleForTesting
+ public void clearSupportedSubscriptionTypesForUnitTest() {
+ mySupportedSubscriptionTypes.clear();
+ }
+
+ /**
+ * If e-mail subscriptions are supported, the From address used when sending e-mails
+ */
+
+ public String getEmailFromAddress() {
+ return myEmailFromAddress;
+ }
+
+ /**
+ * If e-mail subscriptions are supported, the From address used when sending e-mails
+ */
+
+ public void setEmailFromAddress(String theEmailFromAddress) {
+ myEmailFromAddress = theEmailFromAddress;
+ }
+
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty");
@@ -309,5 +353,4 @@ public class ModelConfig {
}
-
}
diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
index 2093f66986c..e59ea7add71 100644
--- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
+++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java
@@ -610,12 +610,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
if (myHasLinks && myResourceLinks != null) {
myResourceLinksField = getResourceLinks()
.stream()
- .map(t->{
- Long retVal = t.getTargetResourcePid();
- return retVal;
- })
+ .map(ResourceLink::getTargetResourcePid)
.filter(Objects::nonNull)
- .map(t->t.toString())
+ .map(Object::toString)
.collect(Collectors.joining(" "));
} else {
myResourceLinksField = null;
diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml
index c5ee9d19bfd..51ddbfd0657 100644
--- a/hapi-fhir-jpaserver-searchparam/pom.xml
+++ b/hapi-fhir-jpaserver-searchparam/pom.xml
@@ -82,6 +82,10 @@
hapi-fhir-validation-resources-r4${project.version}
+
+ javax.annotation
+ javax.annotation-api
+
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java
index 72e35d1db1e..a6b1e6d34ab 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ResourceLinkExtractor.java
@@ -38,9 +38,6 @@ import org.hl7.fhir.r4.model.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import javax.persistence.EntityManager;
-import javax.persistence.PersistenceContext;
-import javax.persistence.PersistenceContextType;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -61,9 +58,6 @@ public class ResourceLinkExtractor {
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
- @PersistenceContext(type = PersistenceContextType.TRANSACTION)
- protected EntityManager myEntityManager;
-
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver) {
String resourceType = theEntity.getResourceType();
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java
index 737b23919c6..b9f658d22a7 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java
@@ -36,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct;
@@ -45,30 +44,20 @@ import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
public abstract class BaseSearchParamRegistry implements ISearchParamRegistry {
+ @Autowired
+ private ModelConfig myModelConfig;
+ @Autowired
+ private ISearchParamProvider mySearchParamProvider;
+ @Autowired
+ private FhirContext myFhirContext;
private static final int MAX_MANAGED_PARAM_COUNT = 10000;
private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
private Map> myBuiltInSearchParams;
private volatile Map> myActiveUniqueSearchParams = Collections.emptyMap();
private volatile Map, List>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap();
- @Autowired
- private FhirContext myCtx;
private volatile Map> myActiveSearchParams;
- @Autowired
- private ModelConfig myModelConfig;
private volatile long myLastRefresh;
- private ApplicationContext myApplicationContext;
- private ISearchParamProvider mySearchParamProvider;
-
- public BaseSearchParamRegistry(ISearchParamProvider theSearchParamProvider) {
- super();
- mySearchParamProvider = theSearchParamProvider;
- }
-
- @VisibleForTesting
- public void setSearchParamProvider(ISearchParamProvider theSearchParamProvider) {
- mySearchParamProvider = theSearchParamProvider;
- }
@Override
public void requestRefresh() {
@@ -128,7 +117,7 @@ public abstract class BaseSearchParamRegistry implemen
return Collections.unmodifiableList(retVal);
}
- public Map> getBuiltInSearchParams() {
+ private Map> getBuiltInSearchParams() {
return myBuiltInSearchParams;
}
@@ -220,10 +209,10 @@ public abstract class BaseSearchParamRegistry implemen
public void postConstruct() {
Map> resourceNameToSearchParams = new HashMap<>();
- Set resourceNames = myCtx.getResourceNames();
+ Set resourceNames = myFhirContext.getResourceNames();
for (String resourceName : resourceNames) {
- RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(resourceName);
+ RuntimeResourceDefinition nextResDef = myFhirContext.getResourceDefinition(resourceName);
String nextResourceName = nextResDef.getName();
HashMap nameToParam = new HashMap<>();
resourceNameToSearchParams.put(nextResourceName, nameToParam);
@@ -284,7 +273,7 @@ public abstract class BaseSearchParamRegistry implemen
continue;
}
- for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) {
+ for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myFhirContext, nextSp)) {
if (isBlank(nextBaseName)) {
continue;
}
@@ -349,5 +338,9 @@ public abstract class BaseSearchParamRegistry implemen
return getActiveSearchParams(theResourceDef.getName()).values();
}
-
+ @VisibleForTesting
+ @Override
+ public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
+ mySearchParamProvider = theSearchParamProvider;
+ }
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java
index 9d44ed27be5..dc3d021dd87 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java
@@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
+import com.google.common.annotations.VisibleForTesting;
import java.util.Collection;
import java.util.List;
@@ -59,4 +60,7 @@ public interface ISearchParamRegistry {
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
+
+ @VisibleForTesting
+ void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider);
}
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java
index 20d7bf3d6a4..3dfb2ffec51 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu2.java
@@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry {
-public SearchParamRegistryDstu2(ISearchParamProvider theSearchParamProvider) {
- super(theSearchParamProvider);
- }
-
@Override
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode();
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java
index cb4e6c065aa..192bcd36e8e 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryDstu3.java
@@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
- public SearchParamRegistryDstu3(ISearchParamProvider theSearchParamProvider) {
- super(theSearchParamProvider);
- }
-
@Override
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode();
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java
index 6ecdd680093..3198a9fbf70 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryR4.java
@@ -41,10 +41,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
- public SearchParamRegistryR4(ISearchParamProvider theSearchParamProvider) {
- super(theSearchParamProvider);
- }
-
@Override
protected RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode();
@@ -128,6 +124,4 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return null;
}
+
+ @Override
+ public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
+ // nothing
+ }
};
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml
index 5dfb07771a6..49c0b24cfbf 100644
--- a/hapi-fhir-jpaserver-subscription/pom.xml
+++ b/hapi-fhir-jpaserver-subscription/pom.xml
@@ -29,6 +29,47 @@
hapi-fhir-validation${project.version}
+
+ org.springframework
+ spring-messaging
+
+
+ org.springframework
+ spring-context
+
+
+ xml-apis
+ xml-apis
+
+
+
+
+ org.thymeleaf
+ thymeleaf
+
+
+ org.thymeleaf
+ thymeleaf-spring5
+
+
+ org.springframework
+ spring-websocket
+
+
+ org.springframework
+ spring-context-support
+
+
+ javax.mail
+ javax.mail-api
+ provided
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
@@ -46,6 +87,16 @@
mockito-coretest
+
+ org.eclipse.jetty
+ jetty-server
+ test
+
+
+ org.eclipse.jetty
+ jetty-servlet
+ test
+
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java
similarity index 96%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java
index d1c561db64c..f63a3fa0983 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/CanonicalSubscription.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscription.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -54,7 +54,7 @@ public class CanonicalSubscription implements Serializable {
@JsonProperty("headers")
private List myHeaders;
@JsonProperty("channelType")
- private Subscription.SubscriptionChannelType myChannelType;
+ private CanonicalSubscriptionChannelType myChannelType;
@JsonProperty("status")
private Subscription.SubscriptionStatus myStatus;
@JsonProperty("triggerDefinition")
@@ -73,11 +73,11 @@ public class CanonicalSubscription implements Serializable {
}
- public Subscription.SubscriptionChannelType getChannelType() {
+ public CanonicalSubscriptionChannelType getChannelType() {
return myChannelType;
}
- public void setChannelType(Subscription.SubscriptionChannelType theChannelType) {
+ public void setChannelType(CanonicalSubscriptionChannelType theChannelType) {
myChannelType = theChannelType;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java
new file mode 100644
index 00000000000..eaffe000315
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/CanonicalSubscriptionChannelType.java
@@ -0,0 +1,135 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.hl7.fhir.exceptions.FHIRException;
+
+public enum CanonicalSubscriptionChannelType {
+ /**
+ * The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made.
+ */
+ RESTHOOK,
+ /**
+ * The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL.
+ */
+ WEBSOCKET,
+ /**
+ * The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:).
+ */
+ EMAIL,
+ /**
+ * The channel is executed by sending an SMS message to the phone number identified in the URL (tel:).
+ */
+ SMS,
+ /**
+ * The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI.
+ */
+ MESSAGE,
+ /**
+ * added to help the parsers with the generic types
+ */
+ NULL;
+
+ public static CanonicalSubscriptionChannelType fromCode(String codeString) throws FHIRException {
+ if (codeString == null || "".equals(codeString))
+ return null;
+ if ("rest-hook".equals(codeString))
+ return RESTHOOK;
+ if ("websocket".equals(codeString))
+ return WEBSOCKET;
+ if ("email".equals(codeString))
+ return EMAIL;
+ if ("sms".equals(codeString))
+ return SMS;
+ if ("message".equals(codeString))
+ return MESSAGE;
+ else
+ throw new FHIRException("Unknown SubscriptionChannelType code '" + codeString + "'");
+ }
+
+ public String toCode() {
+ switch (this) {
+ case RESTHOOK:
+ return "rest-hook";
+ case WEBSOCKET:
+ return "websocket";
+ case EMAIL:
+ return "email";
+ case SMS:
+ return "sms";
+ case MESSAGE:
+ return "message";
+ default:
+ return "?";
+ }
+ }
+
+ public String getSystem() {
+ switch (this) {
+ case RESTHOOK:
+ return "http://hl7.org/fhir/subscription-channel-type";
+ case WEBSOCKET:
+ return "http://hl7.org/fhir/subscription-channel-type";
+ case EMAIL:
+ return "http://hl7.org/fhir/subscription-channel-type";
+ case SMS:
+ return "http://hl7.org/fhir/subscription-channel-type";
+ case MESSAGE:
+ return "http://hl7.org/fhir/subscription-channel-type";
+ default:
+ return "?";
+ }
+ }
+
+ public String getDefinition() {
+ switch (this) {
+ case RESTHOOK:
+ return "The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made.";
+ case WEBSOCKET:
+ return "The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL.";
+ case EMAIL:
+ return "The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:).";
+ case SMS:
+ return "The channel is executed by sending an SMS message to the phone number identified in the URL (tel:).";
+ case MESSAGE:
+ return "The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI.";
+ default:
+ return "?";
+ }
+ }
+
+ public String getDisplay() {
+ switch (this) {
+ case RESTHOOK:
+ return "Rest Hook";
+ case WEBSOCKET:
+ return "Websocket";
+ case EMAIL:
+ return "Email";
+ case SMS:
+ return "SMS";
+ case MESSAGE:
+ return "Message";
+ default:
+ return "?";
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java
new file mode 100644
index 00000000000..d2ba6986278
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/LinkedBlockingQueueSubscriptionChannel.java
@@ -0,0 +1,32 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class LinkedBlockingQueueSubscriptionChannel extends SubscriptionChannel {
+
+ public LinkedBlockingQueueSubscriptionChannel(String theThreadNamingPattern) {
+ super(new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE), theThreadNamingPattern);
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java
similarity index 89%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java
index c02bae8cf16..4560750cf2a 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedMessage.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedMessage.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module;
/*-
* #%L
@@ -53,6 +53,18 @@ public class ResourceModifiedMessage {
@JsonIgnore
private transient IBaseResource myPayloadDecoded;
+ // For JSON
+ public ResourceModifiedMessage() {
+ }
+
+ public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) {
+ setId(theResource.getIdElement());
+ setOperationType(theOperationType);
+ if (theOperationType != OperationTypeEnum.DELETE) {
+ setNewPayload(theFhirContext, theResource);
+ }
+ }
+
public String getPayloadId() {
return myPayloadId;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java
new file mode 100644
index 00000000000..055bf3fd785
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/SubscriptionChannel.java
@@ -0,0 +1,103 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
+import ca.uhn.fhir.util.StopWatch;
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang3.concurrent.BasicThreadFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.messaging.support.ChannelInterceptor;
+import org.springframework.messaging.support.ExecutorSubscribableChannel;
+
+import java.util.ArrayList;
+import java.util.concurrent.*;
+
+public class SubscriptionChannel implements SubscribableChannel {
+ private Logger ourLog = LoggerFactory.getLogger(SubscriptionChannel.class);
+
+ private final ExecutorSubscribableChannel mySubscribableChannel;
+ private final BlockingQueue myQueue;
+
+ public SubscriptionChannel(BlockingQueue theQueue, String theThreadNamingPattern) {
+
+ ThreadFactory threadFactory = new BasicThreadFactory.Builder()
+ .namingPattern(theThreadNamingPattern)
+ .daemon(false)
+ .priority(Thread.NORM_PRIORITY)
+ .build();
+ RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> {
+ ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", theQueue.size());
+ StopWatch sw = new StopWatch();
+ try {
+ theQueue.put(theRunnable);
+ } catch (InterruptedException theE) {
+ throw new RejectedExecutionException("Task " + theRunnable.toString() +
+ " rejected from " + theE.toString());
+ }
+ ourLog.info("Slot become available after {}ms", sw.getMillis());
+ };
+ ThreadPoolExecutor executor = new ThreadPoolExecutor(
+ 1,
+ SubscriptionConstants.EXECUTOR_THREAD_COUNT,
+ 0L,
+ TimeUnit.MILLISECONDS,
+ theQueue,
+ threadFactory,
+ rejectedExecutionHandler);
+ myQueue = theQueue;
+ mySubscribableChannel = new ExecutorSubscribableChannel(executor);
+ }
+
+ @Override
+ public boolean subscribe(MessageHandler handler) {
+ return mySubscribableChannel.subscribe(handler);
+ }
+
+ @Override
+ public boolean unsubscribe(MessageHandler handler) {
+ return mySubscribableChannel.unsubscribe(handler);
+ }
+
+ @Override
+ public boolean send(Message> message, long timeout) {
+ return mySubscribableChannel.send(message, timeout);
+ }
+
+ @VisibleForTesting
+ public void clearInterceptorsForUnitTest() {
+ mySubscribableChannel.setInterceptors(new ArrayList<>());
+ }
+
+ @VisibleForTesting
+ public void addInterceptorForUnitTest(ChannelInterceptor theInterceptor) {
+ mySubscribableChannel.addInterceptor(theInterceptor);
+ }
+
+ @VisibleForTesting
+ public int getQueueSizeForUnitTest() {
+ return myQueue.size();
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java
new file mode 100644
index 00000000000..c16ee936082
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscription.java
@@ -0,0 +1,93 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import com.google.common.annotations.VisibleForTesting;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.SubscribableChannel;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+public class ActiveSubscription {
+ private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
+
+ private final CanonicalSubscription mySubscription;
+ private final SubscribableChannel mySubscribableChannel;
+ private final Collection myDeliveryHandlerSet = new HashSet<>();
+
+ public ActiveSubscription(CanonicalSubscription theSubscription, SubscribableChannel theSubscribableChannel) {
+ mySubscription = theSubscription;
+ mySubscribableChannel = theSubscribableChannel;
+ }
+
+ public CanonicalSubscription getSubscription() {
+ return mySubscription;
+ }
+
+ public SubscribableChannel getSubscribableChannel() {
+ return mySubscribableChannel;
+ }
+
+ public void register(MessageHandler theHandler) {
+ mySubscribableChannel.subscribe(theHandler);
+ myDeliveryHandlerSet.add(theHandler);
+ }
+
+ public void unregister(MessageHandler theMessageHandler) {
+ if (mySubscribableChannel != null) {
+ mySubscribableChannel.unsubscribe(theMessageHandler);
+ if (mySubscribableChannel instanceof DisposableBean) {
+ try {
+ ((DisposableBean) mySubscribableChannel).destroy();
+ } catch (Exception e) {
+ ourLog.error("Failed to destroy channel bean", e);
+ }
+ }
+ }
+
+ }
+
+ public void unregisterAll() {
+ for (MessageHandler messageHandler : myDeliveryHandlerSet) {
+ unregister(messageHandler);
+ }
+ }
+
+ public IIdType getIdElement(FhirContext theFhirContext) {
+ return mySubscription.getIdElement(theFhirContext);
+ }
+
+ public String getCriteriaString() {
+ return mySubscription.getCriteriaString();
+ }
+
+ @VisibleForTesting
+ public MessageHandler getDeliveryHandlerForUnitTest() {
+ return myDeliveryHandlerSet.iterator().next();
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java
new file mode 100644
index 00000000000..93745ebc6e7
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java
@@ -0,0 +1,72 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.apache.commons.lang3.Validate;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ActiveSubscriptionCache {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ActiveSubscriptionCache.class);
+
+ private final Map myCache = new ConcurrentHashMap<>();
+
+ public ActiveSubscription get(String theIdPart) {
+ return myCache.get(theIdPart);
+ }
+
+ public Collection getAll() {
+ return Collections.unmodifiableCollection(myCache.values());
+ }
+
+ public int size() {
+ return myCache.size();
+ }
+
+ public void put(String theSubscriptionId, ActiveSubscription theValue) {
+ myCache.put(theSubscriptionId, theValue);
+ }
+
+ public void remove(String theSubscriptionId) {
+ Validate.notBlank(theSubscriptionId);
+
+ ActiveSubscription activeSubscription = myCache.get(theSubscriptionId);
+ if (activeSubscription == null) {
+ return;
+ }
+
+ activeSubscription.unregisterAll();
+ myCache.remove(theSubscriptionId);
+ }
+
+ public void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) {
+ for (String next : new ArrayList<>(myCache.keySet())) {
+ if (!theAllIds.contains(next)) {
+ ourLog.info("Unregistering Subscription/{}", next);
+ remove(next);
+ }
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java
new file mode 100644
index 00000000000..6abeda02c3f
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/BlockingQueueSubscriptionChannelFactory.java
@@ -0,0 +1,42 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
+import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel;
+
+public class BlockingQueueSubscriptionChannelFactory implements ISubscriptionChannelFactory {
+
+ @Override
+ public SubscriptionChannel newDeliveryChannel(String theSubscriptionId, String theChannelType) {
+ String threadName = "subscription-delivery-" +
+ theChannelType +
+ "-" +
+ theSubscriptionId +
+ "-%d";
+ return new LinkedBlockingQueueSubscriptionChannel(threadName);
+ }
+
+ @Override
+ public SubscriptionChannel newMatchingChannel(String theChannelName) {
+ return new LinkedBlockingQueueSubscriptionChannel(theChannelName + "-%d");
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java
new file mode 100644
index 00000000000..e514906f3e0
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionChannelFactory.java
@@ -0,0 +1,29 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.springframework.messaging.SubscribableChannel;
+
+public interface ISubscriptionChannelFactory {
+ SubscribableChannel newDeliveryChannel(String theSubscriptionId, String theChannelType);
+
+ SubscribableChannel newMatchingChannel(String theChannelName);
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java
new file mode 100644
index 00000000000..49e576cde64
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ISubscriptionProvider.java
@@ -0,0 +1,31 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+public interface ISubscriptionProvider {
+ IBundleProvider search(SearchParameterMap theMap);
+
+ boolean loadSubscription(IBaseResource theResource);
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java
new file mode 100644
index 00000000000..80a2284666f
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionCannonicalizer.java
@@ -0,0 +1,169 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Extension;
+import org.hl7.fhir.r4.model.Subscription;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class SubscriptionCannonicalizer {
+ @Autowired
+ FhirContext myFhirContext;
+
+ public CanonicalSubscription canonicalize(S theSubscription) {
+ switch (myFhirContext.getVersion().getVersion()) {
+ case DSTU2:
+ return canonicalizeDstu2(theSubscription);
+ case DSTU3:
+ return canonicalizeDstu3(theSubscription);
+ case R4:
+ return canonicalizeR4(theSubscription);
+ default:
+ throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion());
+ }
+ }
+
+ protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
+ ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
+
+ CanonicalSubscription retVal = new CanonicalSubscription();
+ try {
+ retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
+ retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType()));
+ retVal.setCriteriaString(subscription.getCriteria());
+ retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
+ retVal.setHeaders(subscription.getChannel().getHeader());
+ retVal.setIdElement(subscription.getIdElement());
+ retVal.setPayloadString(subscription.getChannel().getPayload());
+ } catch (FHIRException theE) {
+ throw new InternalErrorException(theE);
+ }
+ return retVal;
+ }
+
+ protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
+ org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
+
+ CanonicalSubscription retVal = new CanonicalSubscription();
+ try {
+ retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
+ retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
+ retVal.setCriteriaString(subscription.getCriteria());
+ retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
+ retVal.setHeaders(subscription.getChannel().getHeader());
+ retVal.setIdElement(subscription.getIdElement());
+ retVal.setPayloadString(subscription.getChannel().getPayload());
+
+ if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
+ String from;
+ String subjectTemplate;
+ String bodyTemplate;
+ try {
+ from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
+ subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
+ } catch (FHIRException theE) {
+ throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
+ }
+ retVal.getEmailDetails().setFrom(from);
+ retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
+ }
+
+ if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
+ String stripVersionIds;
+ String deliverLatestVersion;
+ try {
+ stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
+ deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
+ } catch (FHIRException theE) {
+ throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
+ }
+ retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
+ retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
+ }
+
+ } catch (FHIRException theE) {
+ throw new InternalErrorException(theE);
+ }
+ return retVal;
+ }
+
+ protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
+ org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
+
+ CanonicalSubscription retVal = new CanonicalSubscription();
+ retVal.setStatus(subscription.getStatus());
+ retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
+ retVal.setCriteriaString(subscription.getCriteria());
+ retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
+ retVal.setHeaders(subscription.getChannel().getHeader());
+ retVal.setIdElement(subscription.getIdElement());
+ retVal.setPayloadString(subscription.getChannel().getPayload());
+
+ if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
+ String from;
+ String subjectTemplate;
+ try {
+ from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
+ subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
+ } catch (FHIRException theE) {
+ throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
+ }
+ retVal.getEmailDetails().setFrom(from);
+ retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
+ }
+
+ if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
+ String stripVersionIds;
+ String deliverLatestVersion;
+ try {
+ stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
+ deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
+ } catch (FHIRException theE) {
+ throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
+ }
+ retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
+ retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
+ }
+
+ List topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
+ if (topicExts.size() > 0) {
+ IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
+ if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
+ throw new PreconditionFailedException("Topic reference must be an EventDefinition");
+ }
+ }
+
+ return retVal;
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java
new file mode 100644
index 00000000000..0c7461a5e3d
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionConstants.java
@@ -0,0 +1,90 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+public class SubscriptionConstants {
+
+ /**
+ *
+ * This extension should be of type string and should be
+ * placed on the Subscription.channel element
+ *
+ */
+ public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
+
+ /**
+ *
+ * This extension should be of type string and should be
+ * placed on the Subscription.channel element
+ *
+ */
+ public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
+
+
+ /**
+ * This extension URL indicates whether a REST HOOK delivery should
+ * include the version ID when delivering.
+ *
+ * This extension should be of type boolean and should be
+ * placed on the Subscription.channel element.
+ *
+ */
+ public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
+
+ /**
+ * This extension URL indicates whether a REST HOOK delivery should
+ * reload the resource and deliver the latest version always. This
+ * could be useful for example if a resource which triggers a
+ * subscription gets updated many times in short succession and there
+ * is no value in delivering the older versions.
+ *
+ * Note that if the resource is now deleted, this may cause
+ * the delivery to be cancelled altogether.
+ *
+ *
+ *
+ * This extension should be of type boolean and should be
+ * placed on the Subscription.channel element.
+ *
+ */
+ public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
+
+ /**
+ * The number of threads used in subscription channel processing
+ */
+ public static final int EXECUTOR_THREAD_COUNT = 5;
+
+ /**
+ * The maximum number of subscriptions that can be active at once
+ */
+
+ public static final int MAX_SUBSCRIPTION_RESULTS = 1000;
+
+ /**
+ * The size of the queue used for sending resources to the subscription matching processor
+ */
+ 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;
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java
new file mode 100644
index 00000000000..9e2fcc32382
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionDeliveryHandlerFactory.java
@@ -0,0 +1,57 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber;
+import org.hl7.fhir.r4.model.Subscription;
+import org.springframework.beans.factory.annotation.Lookup;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+@Component
+public abstract class SubscriptionDeliveryHandlerFactory {
+ private IEmailSender myEmailSender;
+
+ @Lookup
+ protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender);
+ @Lookup
+ protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber();
+
+ public Optional createDeliveryHandler(CanonicalSubscription theSubscription) {
+ if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
+ return Optional.of(getSubscriptionDeliveringEmailSubscriber(myEmailSender));
+ } else if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
+ return Optional.of(getSubscriptionDeliveringRestHookSubscriber());
+ } else {
+ return Optional.empty();
+ }
+ }
+
+ public void setEmailSender(IEmailSender theEmailSender) {
+ myEmailSender = theEmailSender;
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java
new file mode 100644
index 00000000000..6626d0a3b8b
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionLoader.java
@@ -0,0 +1,123 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.param.TokenParam;
+import com.google.common.annotations.VisibleForTesting;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Subscription;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+
+@Service
+@Lazy
+public class SubscriptionLoader {
+ private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionLoader.class);
+
+ @Autowired
+ private ISubscriptionProvider mySubscriptionProvidor;
+ @Autowired
+ private SubscriptionRegistry mySubscriptionRegistry;
+
+ private final Object myInitSubscriptionsLock = new Object();
+ private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
+
+ @PostConstruct
+ public void start() {
+ initSubscriptions();
+ }
+
+ /**
+ * Read the existing subscriptions from the database
+ */
+ @SuppressWarnings("unused")
+ @Scheduled(fixedDelay = 60000)
+ public void initSubscriptions() {
+ if (!myInitSubscriptionsSemaphore.tryAcquire()) {
+ return;
+ }
+ try {
+ doInitSubscriptions();
+ } finally {
+ myInitSubscriptionsSemaphore.release();
+ }
+ }
+
+ @VisibleForTesting
+ public int doInitSubscriptionsForUnitTest() {
+ return doInitSubscriptions();
+ }
+
+ private int doInitSubscriptions() {
+ synchronized (myInitSubscriptionsLock) {
+ ourLog.debug("Starting init subscriptions");
+ SearchParameterMap map = new SearchParameterMap();
+ map.add(Subscription.SP_STATUS, new TokenOrListParam()
+ .addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
+ .addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
+ map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS);
+
+ IBundleProvider subscriptionBundleList = mySubscriptionProvidor.search(map);
+
+ if (subscriptionBundleList.size() >= SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS) {
+ ourLog.error("Currently over " + SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
+ }
+
+ List resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
+
+ Set allIds = new HashSet<>();
+ int changesCount = 0;
+ for (IBaseResource resource : resourceList) {
+ String nextId = resource.getIdElement().getIdPart();
+ allIds.add(nextId);
+ boolean changed = mySubscriptionProvidor.loadSubscription(resource);
+ if (changed) {
+ changesCount++;
+ }
+ }
+
+ mySubscriptionRegistry.unregisterAllSubscriptionsNotInCollection(allIds);
+ ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
+
+ return changesCount;
+ }
+ }
+
+ @VisibleForTesting
+ public void setSubscriptionProviderForUnitTest(ISubscriptionProvider theSubscriptionProvider) {
+ mySubscriptionProvidor = theSubscriptionProvider;
+ }
+}
+
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java
new file mode 100644
index 00000000000..9b170b1dbd6
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/SubscriptionRegistry.java
@@ -0,0 +1,136 @@
+package ca.uhn.fhir.jpa.subscription.module.cache;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import org.apache.commons.lang3.Validate;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.SubscribableChannel;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PreDestroy;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Optional;
+
+/**
+ *
+ * Cache of active subscriptions. When a new subscription is added to the cache, a new Spring Channel is created
+ * and a new MessageHandler for that subscription is subscribed to that channel. These subscriptions, channels, and
+ * handlers are all caches in this registry so they can be removed it the subscription is deleted.
+ */
+
+@Component
+public class SubscriptionRegistry {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionRegistry.class);
+
+ @Autowired
+ SubscriptionCannonicalizer mySubscriptionCanonicalizer;
+ @Autowired
+ SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory;
+ @Autowired
+ ISubscriptionChannelFactory mySubscriptionDeliveryChannelFactory;
+
+ private final ActiveSubscriptionCache myActiveSubscriptionCache = new ActiveSubscriptionCache();
+
+ public ActiveSubscription get(String theIdPart) {
+ return myActiveSubscriptionCache.get(theIdPart);
+ }
+
+ public Collection getAll() {
+ return myActiveSubscriptionCache.getAll();
+ }
+
+ private Optional hasSubscription(IIdType theId) {
+ Validate.notNull(theId);
+ Validate.notBlank(theId.getIdPart());
+ Optional activeSubscription = Optional.ofNullable(myActiveSubscriptionCache.get(theId.getIdPart()));
+ return activeSubscription.map(ActiveSubscription::getSubscription);
+ }
+
+ @SuppressWarnings("UnusedReturnValue")
+ public CanonicalSubscription registerSubscription(IIdType theId, IBaseResource theSubscription) {
+ Validate.notNull(theId);
+ String subscriptionId = theId.getIdPart();
+ Validate.notBlank(subscriptionId);
+ Validate.notNull(theSubscription);
+
+ CanonicalSubscription canonicalized = mySubscriptionCanonicalizer.canonicalize(theSubscription);
+ SubscribableChannel deliveryChannel = mySubscriptionDeliveryChannelFactory.newDeliveryChannel(subscriptionId, canonicalized.getChannelType().toCode().toLowerCase());
+ Optional deliveryHandler = mySubscriptionDeliveryHandlerFactory.createDeliveryHandler(canonicalized);
+
+ ActiveSubscription activeSubscription = new ActiveSubscription(canonicalized, deliveryChannel);
+ myActiveSubscriptionCache.put(subscriptionId, activeSubscription);
+
+ deliveryHandler.ifPresent(handler -> activeSubscription.register(handler));
+
+ return canonicalized;
+ }
+
+ public void unregisterSubscription(IIdType theId) {
+ Validate.notNull(theId);
+ String subscriptionId = theId.getIdPart();
+ myActiveSubscriptionCache.remove(subscriptionId);
+ }
+
+ @PreDestroy
+ public void preDestroy() {
+ unregisterAllSubscriptionsNotInCollection(Collections.emptyList());
+ }
+
+ public void unregisterAllSubscriptionsNotInCollection(Collection theAllIds) {
+ myActiveSubscriptionCache.unregisterAllSubscriptionsNotInCollection(theAllIds);
+ }
+
+ public synchronized boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) {
+ Optional existingSubscription = hasSubscription(theSubscription.getIdElement());
+ CanonicalSubscription newSubscription = mySubscriptionCanonicalizer.canonicalize(theSubscription);
+
+ if (existingSubscription.isPresent()) {
+ if (newSubscription.equals(existingSubscription.get())) {
+ // No changes
+ return false;
+ }
+ ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
+ unregisterSubscription(theSubscription.getIdElement());
+ } else {
+ ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
+ }
+ registerSubscription(theSubscription.getIdElement(), theSubscription);
+ return true;
+ }
+
+ public boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) {
+ if (hasSubscription(theSubscription.getIdElement()).isPresent()) {
+ ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue());
+ unregisterSubscription(theSubscription.getIdElement());
+ return true;
+ }
+ return false;
+ }
+
+ public int size() {
+ return myActiveSubscriptionCache.size();
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java
similarity index 67%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java
index 18548668e27..4e5fdab2e18 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionConfig.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/BaseSubscriptionConfig.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.config;
+package ca.uhn.fhir.jpa.subscription.module.config;
/*-
* #%L
@@ -21,24 +21,19 @@ package ca.uhn.fhir.jpa.subscription.config;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
-import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider;
-import ca.uhn.fhir.rest.client.api.IGenericClient;
-import org.springframework.beans.factory.annotation.Autowired;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
+import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
-@ComponentScan(basePackages = "ca.uhn.fhir.jpa")
+@ComponentScan(basePackages = {"ca.uhn.fhir.jpa.searchparam", "ca.uhn.fhir.jpa.subscription.module"})
public abstract class BaseSubscriptionConfig {
- @Autowired
- IGenericClient myClient;
-
public abstract FhirContext fhirContext();
@Bean
- protected ISearchParamProvider searchParamProvider() {
- return new FhirClientSearchParamProvider(myClient);
+ public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() {
+ return new BlockingQueueSubscriptionChannelFactory();
}
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java
similarity index 92%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java
index 939b92b0893..c1d2f05f21f 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/config/BaseSubscriptionDstu3Config.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/config/SubscriptionDstu3Config.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.config;
+package ca.uhn.fhir.jpa.subscription.module.config;
/*-
* #%L
@@ -32,7 +32,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
// From BaseDstu3Config
-public class BaseSubscriptionDstu3Config extends BaseSubscriptionConfig {
+public class SubscriptionDstu3Config extends BaseSubscriptionConfig {
@Override
public FhirContext fhirContext() {
return fhirContextDstu3();
@@ -52,7 +52,7 @@ public class BaseSubscriptionDstu3Config extends BaseSubscriptionConfig {
@Bean
public ISearchParamRegistry searchParamRegistry() {
- return new SearchParamRegistryDstu3(searchParamProvider());
+ return new SearchParamRegistryDstu3();
}
@Bean(autowire = Autowire.BY_TYPE)
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java
similarity index 99%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java
index aa9d1fc5eaf..045db3507b8 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/CriteriaResourceMatcher.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/CriteriaResourceMatcher.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
/*-
* #%L
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java
similarity index 86%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java
index 9d645a5b719..d1060308531 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/ISubscriptionMatcher.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/ISubscriptionMatcher.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
/*-
* #%L
@@ -20,7 +20,7 @@ package ca.uhn.fhir.jpa.subscription.matcher;
* #L%
*/
-import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
public interface ISubscriptionMatcher {
SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg);
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java
similarity index 90%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java
index bb9005c6958..9688d4afa03 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemory.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcher.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
/*-
* #%L
@@ -26,16 +26,12 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceLinkExtractor;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
-import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
-import org.springframework.stereotype.Service;
-@Service
-@Lazy
-public class SubscriptionMatcherInMemory implements ISubscriptionMatcher {
+public class InMemorySubscriptionMatcher implements ISubscriptionMatcher {
@Autowired
private FhirContext myContext;
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java
similarity index 97%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java
index 992ab36a074..ac1e8406b7e 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/InlineResourceLinkResolver.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/InlineResourceLinkResolver.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
/*-
* #%L
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java
similarity index 97%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java
index 620116fc4d0..2bc970c559a 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatchResult.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/matcher/SubscriptionMatchResult.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
/*-
* #%L
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java
new file mode 100644
index 00000000000..9575c5a65a3
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientResourceRetriever.java
@@ -0,0 +1,50 @@
+package ca.uhn.fhir.jpa.subscription.module.standalone;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FhirClientResourceRetriever implements IResourceRetriever {
+ private static final Logger ourLog = LoggerFactory.getLogger(FhirClientResourceRetriever.class);
+
+ @Autowired
+ FhirContext myFhirContext;
+ @Autowired
+ IGenericClient myClient;
+
+ @Override
+ public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException {
+ RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType());
+
+ return myClient.search().forResource(resourceDef.getName()).withIdAndCompartment(payloadId.getIdPart(), payloadId.getResourceType()).execute();
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java
similarity index 84%
rename from hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java
index 50b1b5de76c..422d5fd8c1f 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/FhirClientSearchParamProvider.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSearchParamProvider.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.standalone;
/*-
* #%L
@@ -32,11 +32,18 @@ import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import ca.uhn.fhir.util.BundleUtil;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+@Service
public class FhirClientSearchParamProvider implements ISearchParamProvider {
+ private static final Logger ourLog = LoggerFactory.getLogger(FhirClientSearchParamProvider.class);
- private final IGenericClient myClient;
+ private IGenericClient myClient;
+ @Autowired
public FhirClientSearchParamProvider(IGenericClient theClient) {
myClient = theClient;
}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java
new file mode 100644
index 00000000000..de4a7c1d211
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/FhirClientSubscriptionProvider.java
@@ -0,0 +1,71 @@
+package ca.uhn.fhir.jpa.subscription.module.standalone;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
+import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
+import ca.uhn.fhir.rest.api.CacheControlDirective;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.rest.server.SimpleBundleProvider;
+import ca.uhn.fhir.util.BundleUtil;
+import org.hl7.fhir.instance.model.api.IBaseBundle;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class FhirClientSubscriptionProvider implements ISubscriptionProvider {
+ @Autowired
+ private FhirContext myFhirContext;
+ @Autowired
+ private SubscriptionRegistry mySubscriptionRegistry;
+
+ IGenericClient myClient;
+
+ @Autowired
+ public FhirClientSubscriptionProvider(IGenericClient theClient) {
+ myClient = theClient;
+ }
+
+ @Override
+ public IBundleProvider search(SearchParameterMap theMap) {
+ FhirContext fhirContext = myClient.getFhirContext();
+
+ String searchURL = ResourceTypeEnum.SUBSCRIPTION.getCode() + theMap.toNormalizedQueryString(myFhirContext);
+
+ IBaseBundle bundle = myClient
+ .search()
+ .byUrl(searchURL)
+ .cacheControl(new CacheControlDirective().setNoCache(true))
+ .execute();
+
+ return new SimpleBundleProvider(BundleUtil.toListOfResources(fhirContext, bundle));
+ }
+
+ @Override
+ public boolean loadSubscription(IBaseResource theResource) {
+ return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theResource);
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java
new file mode 100644
index 00000000000..35877ac242f
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/standalone/StandaloneSubscriptionMessageHandler.java
@@ -0,0 +1,65 @@
+package ca.uhn.fhir.jpa.subscription.module.standalone;
+
+/*-
+ * #%L
+ * HAPI FHIR Subscription Server
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriber;
+import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
+import org.springframework.messaging.MessagingException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class StandaloneSubscriptionMessageHandler implements MessageHandler {
+ private static final Logger ourLog = LoggerFactory.getLogger(StandaloneSubscriptionMessageHandler.class);
+
+ @Autowired
+ FhirContext myFhirContext;
+ @Autowired
+ SubscriptionCheckingSubscriber mySubscriptionCheckingSubscriber;
+ @Autowired
+ SubscriptionRegistry mySubscriptionRegistry;
+
+ @Override
+ public void handleMessage(Message> theMessage) throws MessagingException {
+ if (!(theMessage instanceof ResourceModifiedJsonMessage)) {
+ ourLog.warn("Unexpected message payload type: {}", theMessage);
+ return;
+ }
+ ResourceModifiedMessage resourceModifiedMessage = ((ResourceModifiedJsonMessage) theMessage).getPayload();
+ IBaseResource resource = resourceModifiedMessage.getNewPayload(myFhirContext);
+ RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(resource);
+
+ if (resourceDef.getName().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
+ mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(resource);
+ }
+ mySubscriptionCheckingSubscriber.handleMessage(theMessage);
+ }
+}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java
similarity index 94%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java
index b70157ef1ff..4608cbe4b19 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseJsonMessage.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseJsonMessage.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java
similarity index 61%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java
index e6bc4412614..2aa8caf1638 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDeliverySubscriber.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/BaseSubscriptionDeliverySubscriber.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -20,23 +20,26 @@ package ca.uhn.fhir.jpa.subscription;
* #L%
*/
-import org.hl7.fhir.r4.model.Subscription;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.context.annotation.Scope;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
+import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
-import org.springframework.stereotype.Component;
-public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptionSubscriber {
+public abstract class BaseSubscriptionDeliverySubscriber implements MessageHandler {
private static final Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionDeliverySubscriber.class);
- public BaseSubscriptionDeliverySubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
- super(theChannelType, theSubscriptionInterceptor);
- }
+ @Autowired
+ protected FhirContext myFhirContext;
+ @Autowired
+ protected SubscriptionRegistry mySubscriptionRegistry;
@Override
- public void handleMessage(Message> theMessage) throws MessagingException {
+ public void handleMessage(Message theMessage) throws MessagingException {
if (!(theMessage.getPayload() instanceof ResourceDeliveryMessage)) {
ourLog.warn("Unexpected payload type: {}", theMessage.getPayload());
return;
@@ -46,15 +49,11 @@ public abstract class BaseSubscriptionDeliverySubscriber extends BaseSubscriptio
try {
ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload();
- subscriptionId = msg.getSubscription().getIdElement(getContext()).getValue();
+ subscriptionId = msg.getSubscription().getIdElement(myFhirContext).getValue();
- CanonicalSubscription updatedSubscription = (CanonicalSubscription) getSubscriptionInterceptor().getIdToSubscription().get(msg.getSubscription().getIdElement(getContext()).getIdPart());
+ ActiveSubscription updatedSubscription = mySubscriptionRegistry.get(msg.getSubscription().getIdElement(myFhirContext).getIdPart());
if (updatedSubscription != null) {
- msg.setSubscription(updatedSubscription);
- }
-
- if (!subscriptionTypeApplies(msg.getSubscription())) {
- return;
+ msg.setSubscription(updatedSubscription.getSubscription());
}
handleMessage(msg);
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java
similarity index 70%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java
index 81af49b16ed..cab184973cc 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/ISubscriptionWebsocketHandler.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/IResourceRetriever.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.websocket;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
-/*
+/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -20,8 +20,9 @@ package ca.uhn.fhir.jpa.subscription.websocket;
* #L%
*/
-import org.springframework.web.socket.WebSocketHandler;
-
-public interface ISubscriptionWebsocketHandler extends WebSocketHandler {
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+public interface IResourceRetriever {
+ IBaseResource getResource(IIdType id);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java
similarity index 94%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java
index 89bd4da618a..c0df987f3e1 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryJsonMessage.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryJsonMessage.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java
similarity index 88%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java
index e4a495d4bc0..94bb7bb4244 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceDeliveryMessage.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceDeliveryMessage.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -21,9 +21,13 @@ package ca.uhn.fhir.jpa.subscription;
*/
import ca.uhn.fhir.context.FhirContext;
-import com.fasterxml.jackson.annotation.*;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.gson.Gson;
-import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java
similarity index 90%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java
index 6d6b8186089..9334b3d7bf3 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/ResourceModifiedJsonMessage.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/ResourceModifiedJsonMessage.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.subscription;
* #L%
*/
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -28,7 +29,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
@JsonAutoDetect(creatorVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class ResourceModifiedJsonMessage extends BaseJsonMessage {
- @JsonProperty("headers")
+ @JsonProperty("payload")
private ResourceModifiedMessage myPayload;
/**
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java
similarity index 55%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java
index 480759e0f34..0e830891f7f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionCheckingSubscriber.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriber.java
@@ -1,33 +1,28 @@
-package ca.uhn.fhir.jpa.subscription;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
-import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
-import ca.uhn.fhir.jpa.subscription.matcher.ISubscriptionMatcher;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
+import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Scope;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Service;
-import java.util.List;
+import java.util.Collection;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -45,20 +40,16 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
* #L%
*/
-@Component
-@Scope("prototype")
-public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
+@Service
+public class SubscriptionCheckingSubscriber implements MessageHandler {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriber.class);
- private final ISubscriptionMatcher mySubscriptionMatcher;
-
@Autowired
- private MatchUrlService myMatchUrlService;
-
- public SubscriptionCheckingSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, ISubscriptionMatcher theSubscriptionMatcher) {
- super(theChannelType, theSubscriptionInterceptor);
- this.mySubscriptionMatcher = theSubscriptionMatcher;
- }
+ private ISubscriptionMatcher mySubscriptionMatcher;
+ @Autowired
+ private FhirContext myFhirContext;
+ @Autowired
+ private SubscriptionRegistry mySubscriptionRegistry;
@Override
public void handleMessage(Message> theMessage) throws MessagingException {
@@ -82,17 +73,17 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
return;
}
- IIdType id = msg.getId(getContext());
+ IIdType id = msg.getId(myFhirContext);
String resourceType = id.getResourceType();
- List subscriptions = getSubscriptionInterceptor().getRegisteredSubscriptions();
+ Collection subscriptions = mySubscriptionRegistry.getAll();
ourLog.trace("Testing {} subscriptions for applicability", subscriptions.size());
- for (CanonicalSubscription nextSubscription : subscriptions) {
+ for (ActiveSubscription nextActiveSubscription : subscriptions) {
- String nextSubscriptionId = nextSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue();
- String nextCriteriaString = nextSubscription.getCriteriaString();
+ String nextSubscriptionId = nextActiveSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue();
+ String nextCriteriaString = nextActiveSubscription.getCriteriaString();
if (isNotBlank(msg.getSubscriptionId())) {
if (!msg.getSubscriptionId().equals(nextSubscriptionId)) {
@@ -125,45 +116,18 @@ public class SubscriptionCheckingSubscriber extends BaseSubscriptionSubscriber {
ourLog.debug("Found match: queueing rest-hook notification for resource: {}", id.toUnqualifiedVersionless().getValue());
ResourceDeliveryMessage deliveryMsg = new ResourceDeliveryMessage();
- deliveryMsg.setPayload(getContext(), msg.getNewPayload(getContext()));
- deliveryMsg.setSubscription(nextSubscription);
+ deliveryMsg.setPayload(myFhirContext, msg.getNewPayload(myFhirContext));
+ deliveryMsg.setSubscription(nextActiveSubscription.getSubscription());
deliveryMsg.setOperationType(msg.getOperationType());
- deliveryMsg.setPayloadId(msg.getId(getContext()));
+ deliveryMsg.setPayloadId(msg.getId(myFhirContext));
ResourceDeliveryJsonMessage wrappedMsg = new ResourceDeliveryJsonMessage(deliveryMsg);
- MessageChannel deliveryChannel = getSubscriptionInterceptor().getDeliveryChannel(nextSubscription);
+ MessageChannel deliveryChannel = nextActiveSubscription.getSubscribableChannel();
if (deliveryChannel != null) {
deliveryChannel.send(wrappedMsg);
} else {
- ourLog.warn("Do not have deliovery channel for subscription {}", nextSubscription.getIdElement(getContext()));
+ ourLog.warn("Do not have deliovery channel for subscription {}", nextActiveSubscription.getIdElement(myFhirContext));
}
}
-
-
}
-
- /**
- * Subclasses may override
- */
- protected String massageCriteria(String theCriteria) {
- return theCriteria;
- }
-
- /**
- * Search based on a query criteria
- */
- protected IBundleProvider performSearch(String theCriteria) {
- RuntimeResourceDefinition responseResourceDef = getSubscriptionDao().validateCriteriaAndReturnResourceDefinition(theCriteria);
- SearchParameterMap responseCriteriaUrl = myMatchUrlService.translateMatchUrl(theCriteria, responseResourceDef);
-
- RequestDetails req = new ServletSubRequestDetails();
- req.setSubRequest(true);
-
- IFhirResourceDao extends IBaseResource> responseDao = getSubscriptionInterceptor().getDao(responseResourceDef.getImplementingClass());
- responseCriteriaUrl.setLoadSynchronousUpTo(1);
-
- IBundleProvider responseResults = responseDao.search(responseCriteriaUrl, req);
- return responseResults;
- }
-
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java
similarity index 79%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java
index dd34f1d5abd..ab635443f35 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/resthook/SubscriptionDeliveringRestHookSubscriber.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionDeliveringRestHookSubscriber.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.resthook;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -20,13 +20,7 @@ package ca.uhn.fhir.jpa.subscription.resthook;
* #L%
*/
-import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber;
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.client.api.*;
@@ -36,9 +30,9 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Component;
@@ -56,9 +50,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDeliverySubscriber {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
- public SubscriptionDeliveringRestHookSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
- super(theChannelType, theSubscriptionInterceptor);
- }
+ @Autowired
+ IResourceRetriever myResourceRetriever;
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription);
@@ -105,7 +98,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
}
break;
case DELETE:
- operation = theClient.delete().resourceById(theMsg.getPayloadId(getContext()));
+ operation = theClient.delete().resourceById(theMsg.getPayloadId(myFhirContext));
break;
default:
ourLog.warn("Ignoring delivery message of type: {}", theMsg.getOperationType());
@@ -116,7 +109,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
operation.encoded(thePayloadType);
}
- ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(getContext()).toUnqualifiedVersionless().getValue());
+ ourLog.info("Delivering {} rest-hook payload {} for {}", theMsg.getOperationType(), thePayloadResource.getIdElement().toUnqualified().getValue(), theSubscription.getIdElement(myFhirContext).toUnqualifiedVersionless().getValue());
try {
operation.execute();
@@ -128,16 +121,14 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
}
protected IBaseResource getAndMassagePayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription) {
- IBaseResource payloadResource = theMsg.getPayload(getContext());
+ IBaseResource payloadResource = theMsg.getPayload(myFhirContext);
if (payloadResource == null || theSubscription.getRestHookDetails().isDeliverLatestVersion()) {
- IIdType payloadId = theMsg.getPayloadId(getContext());
- RuntimeResourceDefinition resourceDef = getContext().getResourceDefinition(payloadId.getResourceType());
- IFhirResourceDao dao = getSubscriptionInterceptor().getDao(resourceDef.getImplementingClass());
+ IIdType payloadId = theMsg.getPayloadId(myFhirContext);
try {
- payloadResource = dao.read(payloadId.toVersionless());
+ payloadResource = myResourceRetriever.getResource(payloadId.toVersionless());
} catch (ResourceGoneException e) {
- ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadId.toVersionless(), theSubscription.getIdElement(getContext()));
+ ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadId.toVersionless(), theSubscription.getIdElement(myFhirContext));
return null;
}
}
@@ -169,10 +160,10 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
}
// Create the client request
- getContext().getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ myFhirContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
IGenericClient client = null;
if (isNotBlank(endpointUrl)) {
- client = getContext().newRestfulGenericClient(endpointUrl);
+ client = myFhirContext.newRestfulGenericClient(endpointUrl);
// Additional headers specified in the subscription
List headers = subscription.getHeaders();
@@ -191,12 +182,11 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
* @param theMsg
*/
protected void sendNotification(ResourceDeliveryMessage theMsg) {
- FhirContext context= getContext();
Map> params = new HashMap();
List headers = new ArrayList<>();
StringBuilder url = new StringBuilder(theMsg.getSubscription().getEndpointUrl());
- IHttpClient client = context.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers);
- IHttpRequest request = client.createParamRequest(context, params, null);
+ IHttpClient client = myFhirContext.getRestfulClientFactory().getHttpClient(url, params, "", RequestTypeEnum.POST, headers);
+ IHttpRequest request = client.createParamRequest(myFhirContext, params, null);
try {
IHttpResponse response = request.execute();
} catch (IOException e) {
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java
similarity index 88%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java
index 33c541c780e..83ee1fb4c76 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/websocket/SubscriptionWebsocketHandler.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionWebsocketHandler.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.websocket;
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
/*
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.subscription.websocket;
*/
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
+import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.IdType;
@@ -32,18 +32,19 @@ import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
+import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
-import java.util.Map;
-public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements ISubscriptionWebsocketHandler {
+public class SubscriptionWebsocketHandler extends TextWebSocketHandler implements WebSocketHandler {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionWebsocketHandler.class);
@Autowired
- private SubscriptionWebsocketInterceptor mySubscriptionWebsocketInterceptor;
+ protected SubscriptionRegistry mySubscriptionRegistry;
+
@Autowired
private FhirContext myCtx;
@@ -102,26 +103,24 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement
private class BoundStaticSubscipriptionState implements IState, MessageHandler {
- private WebSocketSession mySession;
- private CanonicalSubscription mySubscription;
+ private final WebSocketSession mySession;
+ private final ActiveSubscription myActiveSubscription;
- public BoundStaticSubscipriptionState(WebSocketSession theSession, CanonicalSubscription theSubscription) {
+ public BoundStaticSubscipriptionState(WebSocketSession theSession, ActiveSubscription theActiveSubscription) {
mySession = theSession;
- mySubscription = theSubscription;
+ myActiveSubscription = theActiveSubscription;
- String subscriptionId = mySubscription.getIdElement(myCtx).getIdPart();
- mySubscriptionWebsocketInterceptor.registerHandler(subscriptionId, this);
+ theActiveSubscription.register(this);
}
@Override
public void closing() {
- String subscriptionId = mySubscription.getIdElement(myCtx).getIdPart();
- mySubscriptionWebsocketInterceptor.unregisterHandler(subscriptionId, this);
+ myActiveSubscription.unregister(this);
}
private void deliver() {
try {
- String payload = "ping " + mySubscription.getIdElement(myCtx).getIdPart();
+ String payload = "ping " + myActiveSubscription.getIdElement(myCtx).getIdPart();
ourLog.info("Sending WebSocket message: {}", payload);
mySession.sendMessage(new TextMessage(payload));
} catch (IOException e) {
@@ -136,7 +135,7 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement
}
try {
ResourceDeliveryMessage msg = (ResourceDeliveryMessage) theMessage.getPayload();
- if (mySubscription.equals(msg.getSubscription())) {
+ if (myActiveSubscription.getSubscription().equals(msg.getSubscription())) {
deliver();
}
} catch (Exception e) {
@@ -177,9 +176,8 @@ public class SubscriptionWebsocketHandler extends TextWebSocketHandler implement
}
try {
- Map idToSubscription = mySubscriptionWebsocketInterceptor.getIdToSubscription();
- CanonicalSubscription subscription = idToSubscription.get(id.getIdPart());
- myState = new BoundStaticSubscipriptionState( theSession, subscription);
+ ActiveSubscription activeSubscription = mySubscriptionRegistry.get(id.getIdPart());
+ myState = new BoundStaticSubscipriptionState( theSession, activeSubscription);
} catch (ResourceNotFoundException e) {
try {
String message = "Invalid bind request - Unknown subscription: " + id.getValue();
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java
similarity index 94%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java
index 6129ecdc5fc..7fa707b85ae 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/EmailDetails.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/EmailDetails.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.email;
+package ca.uhn.fhir.jpa.subscription.module.subscriber.email;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java
similarity index 88%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java
index 55b0d7f5a08..4a6df533d1c 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/IEmailSender.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/IEmailSender.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.email;
+package ca.uhn.fhir.jpa.subscription.module.subscriber.email;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java
similarity index 98%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java
index c4c101e5cca..1d0d5be09a5 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/JavaMailEmailSender.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/JavaMailEmailSender.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.email;
+package ca.uhn.fhir.jpa.subscription.module.subscriber.email;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java
similarity index 72%
rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java
rename to hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java
index cb659226e97..bf15c92fa57 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/email/SubscriptionDeliveringEmailSubscriber.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/email/SubscriptionDeliveringEmailSubscriber.java
@@ -1,8 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.email;
+package ca.uhn.fhir.jpa.subscription.module.subscriber.email;
/*-
* #%L
- * HAPI FHIR JPA Server
+ * HAPI FHIR Subscription Server
* %%
* Copyright (C) 2014 - 2018 University Health Network
* %%
@@ -20,13 +20,14 @@ package ca.uhn.fhir.jpa.subscription.email;
* #L%
*/
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDeliverySubscriber;
-import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
-import ca.uhn.fhir.jpa.subscription.ResourceDeliveryMessage;
+import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseSubscriptionDeliverySubscriber;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@@ -37,16 +38,17 @@ import static org.apache.commons.lang3.StringUtils.*;
@Component
@Scope("prototype")
-
public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliverySubscriber {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringEmailSubscriber.class);
- private SubscriptionEmailInterceptor mySubscriptionEmailInterceptor;
+ @Autowired
+ private ModelConfig myModelConfig;
- public SubscriptionDeliveringEmailSubscriber(Subscription.SubscriptionChannelType theChannelType, SubscriptionEmailInterceptor theSubscriptionEmailInterceptor) {
- super(theChannelType, theSubscriptionEmailInterceptor);
+ private IEmailSender myEmailSender;
- mySubscriptionEmailInterceptor = theSubscriptionEmailInterceptor;
+ @Autowired
+ public SubscriptionDeliveringEmailSubscriber(IEmailSender theEmailSender) {
+ myEmailSender = theEmailSender;
}
@Override
@@ -64,7 +66,7 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
}
}
- String from = processEmailAddressUri(defaultString(subscription.getEmailDetails().getFrom(), mySubscriptionEmailInterceptor.getDefaultFromAddress()));
+ String from = processEmailAddressUri(defaultString(subscription.getEmailDetails().getFrom(), myModelConfig.getEmailFromAddress()));
String subjectTemplate = defaultString(subscription.getEmailDetails().getSubjectTemplate(), provideDefaultSubjectTemplate());
EmailDetails details = new EmailDetails();
@@ -72,10 +74,9 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
details.setFrom(from);
details.setBodyTemplate(subscription.getPayloadString());
details.setSubjectTemplate(subjectTemplate);
- details.setSubscription(subscription.getIdElement(getContext()));
+ details.setSubscription(subscription.getIdElement(myFhirContext));
- IEmailSender emailSender = mySubscriptionEmailInterceptor.getEmailSender();
- emailSender.send(details);
+ myEmailSender.send(details);
}
private String processEmailAddressUri(String next) {
@@ -86,8 +87,11 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
return next;
}
-
private String provideDefaultSubjectTemplate() {
return "HAPI FHIR Subscriptions";
}
+
+ public void setEmailSender(IEmailSender theEmailSender) {
+ myEmailSender = theEmailSender;
+ }
}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java
deleted file mode 100644
index b06f3434592..00000000000
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionDstu3Test.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package ca.uhn.fhir.jpa.subscription;
-
-import ca.uhn.fhir.jpa.subscription.config.TestSubscriptionDstu3Config;
-import org.springframework.test.context.ContextConfiguration;
-
-@ContextConfiguration(classes = {TestSubscriptionDstu3Config.class})
-public abstract class BaseSubscriptionDstu3Test extends BaseSubscriptionTest {
-}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java
deleted file mode 100644
index c62a44c766e..00000000000
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package ca.uhn.fhir.jpa.subscription;
-
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
-import ca.uhn.fhir.jpa.subscription.config.MockSearchParamProvider;
-import ca.uhn.fhir.rest.api.server.IBundleProvider;
-import org.junit.runner.RunWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
-@RunWith(SpringJUnit4ClassRunner.class)
-public abstract class BaseSubscriptionTest {
-
- @Autowired
- ISearchParamProvider mySearchParamProvider;
-
- @Autowired
- ISearchParamRegistry mySearchParamRegistry;
-
- public void setSearchParamBundleResponse(IBundleProvider theBundleProvider) {
- ((MockSearchParamProvider)mySearchParamProvider).setBundleProvider(theBundleProvider);
- mySearchParamRegistry.forceRefresh();
- }
-
-
-}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java
deleted file mode 100644
index be4dc5d128b..00000000000
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionDstu3Config.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package ca.uhn.fhir.jpa.subscription.config;
-
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
-import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
-import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Import;
-
-@Configuration
-@Import(TestSubscriptionConfig.class)
-public class TestSubscriptionDstu3Config extends BaseSubscriptionDstu3Config {
- @Bean
- @Override
- public ISearchParamProvider searchParamProvider() {
- return new MockSearchParamProvider();
- }
-
- @Bean
- @Override
- public ISearchParamRegistry searchParamRegistry() {
- return new SearchParamRegistryDstu3(searchParamProvider());
- }
-
-}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java
new file mode 100644
index 00000000000..8ae2afbc84e
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionDstu3Test.java
@@ -0,0 +1,40 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+import ca.uhn.fhir.jpa.subscription.module.config.TestSubscriptionDstu3Config;
+import ca.uhn.fhir.util.StopWatch;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.springframework.test.context.ContextConfiguration;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.fail;
+
+@ContextConfiguration(classes = {TestSubscriptionDstu3Config.class})
+public abstract class BaseSubscriptionDstu3Test extends BaseSubscriptionTest {
+ public static void waitForSize(int theTarget, List> theList) {
+ StopWatch sw = new StopWatch();
+ while (theList.size() != theTarget && sw.getMillis() <= 16000) {
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException theE) {
+ throw new Error(theE);
+ }
+ }
+ if (sw.getMillis() >= 16000) {
+ String describeResults = theList
+ .stream()
+ .map(t -> {
+ if (t == null) {
+ return "null";
+ }
+ if (t instanceof IBaseResource) {
+ return ((IBaseResource) t).getIdElement().getValue();
+ }
+ return t.toString();
+ })
+ .collect(Collectors.joining(", "));
+ fail("Size " + theList.size() + " is != target " + theTarget + " - Got: " + describeResults);
+ }
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java
new file mode 100644
index 00000000000..e81033463ca
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/BaseSubscriptionTest.java
@@ -0,0 +1,39 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
+import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
+import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider;
+import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public abstract class BaseSubscriptionTest {
+
+ @Autowired
+ ISearchParamProvider mySearchParamProvider;
+
+ @Autowired
+ ISearchParamRegistry mySearchParamRegistry;
+
+ @Autowired
+ ISubscriptionProvider mySubscriptionProvider;
+
+ @Autowired
+ SubscriptionLoader mySubscriptionLoader;
+
+ public void initSearchParamRegistry(IBundleProvider theBundleProvider) {
+ ((MockFhirClientSearchParamProvider)mySearchParamProvider).setBundleProvider(theBundleProvider);
+ mySearchParamRegistry.forceRefresh();
+ }
+
+ public void initSubscriptionLoader(IBundleProvider theBundleProvider) {
+ ((MockFhirClientSubscriptionProvider)mySubscriptionProvider).setBundleProvider(theBundleProvider);
+ mySubscriptionLoader.doInitSubscriptionsForUnitTest();
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java
new file mode 100644
index 00000000000..d35365db71b
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/ResourceModifiedTest.java
@@ -0,0 +1,50 @@
+package ca.uhn.fhir.jpa.subscription.module;
+
+import ca.uhn.fhir.context.FhirContext;
+import org.hl7.fhir.r4.model.Organization;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class ResourceModifiedTest {
+ private FhirContext myFhirContext = FhirContext.forR4();
+
+ @Test
+ public void testCreate() {
+ Organization org = new Organization();
+ org.setName("testOrgName");
+ org.setId("testOrgId");
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.CREATE);
+ assertEquals(org.getIdElement(), msg.getId(myFhirContext));
+ assertEquals(ResourceModifiedMessage.OperationTypeEnum.CREATE, msg.getOperationType());
+ Organization decodedOrg = (Organization) msg.getNewPayload(myFhirContext);
+ assertEquals(org.getId(), decodedOrg.getId());
+ assertEquals(org.getName(), decodedOrg.getName());
+ }
+
+ @Test
+ public void testUpdate() {
+ Organization org = new Organization();
+ org.setName("testOrgName");
+ org.setId("testOrgId");
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
+ assertEquals(org.getIdElement(), msg.getId(myFhirContext));
+ assertEquals(ResourceModifiedMessage.OperationTypeEnum.UPDATE, msg.getOperationType());
+ Organization decodedOrg = (Organization) msg.getNewPayload(myFhirContext);
+ assertEquals(org.getId(), decodedOrg.getId());
+ assertEquals(org.getName(), decodedOrg.getName());
+ }
+
+ @Test
+ public void testDelete() {
+ Organization org = new Organization();
+ org.setName("testOrgName");
+ org.setId("testOrgId");
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, org, ResourceModifiedMessage.OperationTypeEnum.DELETE);
+ assertEquals(org.getIdElement(), msg.getId(myFhirContext));
+ assertEquals(ResourceModifiedMessage.OperationTypeEnum.DELETE, msg.getOperationType());
+ assertNull(msg.getNewPayload(myFhirContext));
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java
similarity index 63%
rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java
rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java
index 071e3c24bce..cbc47ae2e23 100644
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/MockSearchParamProvider.java
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSearchParamProvider.java
@@ -1,14 +1,14 @@
-package ca.uhn.fhir.jpa.subscription.config;
+package ca.uhn.fhir.jpa.subscription.module.config;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
-import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider;
+import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
-public class MockSearchParamProvider extends FhirClientSearchParamProvider {
+public class MockFhirClientSearchParamProvider extends FhirClientSearchParamProvider {
private IBundleProvider myBundleProvider = new SimpleBundleProvider();
- public MockSearchParamProvider() {
+ public MockFhirClientSearchParamProvider() {
super(null);
}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java
new file mode 100644
index 00000000000..efe5326da84
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/MockFhirClientSubscriptionProvider.java
@@ -0,0 +1,24 @@
+package ca.uhn.fhir.jpa.subscription.module.config;
+
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.server.SimpleBundleProvider;
+
+public class MockFhirClientSubscriptionProvider extends FhirClientSubscriptionProvider {
+ private IBundleProvider myBundleProvider = new SimpleBundleProvider();
+
+
+ public MockFhirClientSubscriptionProvider() {
+ super(null);
+ }
+
+ public void setBundleProvider(IBundleProvider theBundleProvider) {
+ myBundleProvider = theBundleProvider;
+ }
+
+ @Override
+ public IBundleProvider search(SearchParameterMap theParams) {
+ return myBundleProvider;
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java
similarity index 76%
rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java
rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java
index a974094f491..a2f4e2cb4cb 100644
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/config/TestSubscriptionConfig.java
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionConfig.java
@@ -1,7 +1,8 @@
-package ca.uhn.fhir.jpa.subscription.config;
+package ca.uhn.fhir.jpa.subscription.module.config;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
+import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.util.PortUtil;
import org.springframework.beans.factory.annotation.Autowired;
@@ -28,4 +29,9 @@ public class TestSubscriptionConfig {
return myFhirContext.newRestfulGenericClient(ourServerBase);
};
+
+ @Bean
+ public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() {
+ return new InMemorySubscriptionMatcher();
+ }
}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java
new file mode 100644
index 00000000000..883fa7c5233
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/config/TestSubscriptionDstu3Config.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.jpa.subscription.module.config;
+
+import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.context.annotation.Primary;
+
+@Configuration
+@Import(TestSubscriptionConfig.class)
+public class TestSubscriptionDstu3Config extends SubscriptionDstu3Config {
+ @Bean
+ @Primary
+ public ISearchParamProvider searchParamProvider() {
+ return new MockFhirClientSearchParamProvider();
+ }
+
+ @Bean
+ @Primary
+ public ISubscriptionProvider subsriptionProvider() { return new MockFhirClientSubscriptionProvider();}
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java
similarity index 96%
rename from hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java
rename to hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java
index ca60086a3ab..4b51e775359 100644
--- a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/matcher/SubscriptionMatcherInMemoryTestR3.java
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/matcher/InMemorySubscriptionMatcherTestR3.java
@@ -1,6 +1,6 @@
-package ca.uhn.fhir.jpa.subscription.matcher;
+package ca.uhn.fhir.jpa.subscription.module.matcher;
-import ca.uhn.fhir.jpa.subscription.BaseSubscriptionDstu3Test;
+import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
import org.hl7.fhir.dstu3.model.*;
@@ -15,23 +15,23 @@ import java.util.Arrays;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test {
+public class InMemorySubscriptionMatcherTestR3 extends BaseSubscriptionDstu3Test {
@Autowired
- SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
+ InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
private void assertUnsupported(IBaseResource resource, String criteria) {
- assertFalse(mySubscriptionMatcherInMemory.match(criteria, resource).supported());
+ assertFalse(myInMemorySubscriptionMatcher.match(criteria, resource).supported());
}
private void assertMatched(IBaseResource resource, String criteria) {
- SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource);
+ SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource);
assertTrue(result.supported());
assertTrue(result.matched());
}
private void assertNotMatched(IBaseResource resource, String criteria) {
- SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, resource);
+ SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, resource);
assertTrue(result.supported());
assertFalse(result.matched());
@@ -282,7 +282,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid");
- setSearchParamBundleResponse(bundle);
+ initSearchParamRegistry(bundle);
{
Provenance prov = new Provenance();
@@ -314,7 +314,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid");
- setSearchParamBundleResponse(bundle);
+ initSearchParamRegistry(bundle);
{
BodySite bodySite = new BodySite();
@@ -406,7 +406,7 @@ public class SubscriptionMatcherInMemoryTestR3 extends BaseSubscriptionDstu3Test
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
IBundleProvider bundle = new SimpleBundleProvider(Arrays.asList(sp), "uuid");
- setSearchParamBundleResponse(bundle);
+ initSearchParamRegistry(bundle);
{
ProcedureRequest pr = new ProcedureRequest();
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java
new file mode 100644
index 00000000000..1c8ba0b36c0
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/BaseSubscriptionChannelDstu3Test.java
@@ -0,0 +1,175 @@
+package ca.uhn.fhir.jpa.subscription.module.standalone;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.jpa.subscription.module.BaseSubscriptionDstu3Test;
+import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
+import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage;
+import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriberTest;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+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.util.PortUtil;
+import com.google.common.collect.Lists;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.hl7.fhir.dstu3.model.*;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.messaging.SubscribableChannel;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class BaseSubscriptionChannelDstu3Test extends BaseSubscriptionDstu3Test {
+ private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriberTest.class);
+
+ @Autowired
+ FhirContext myFhirContext;
+ @Autowired
+ StandaloneSubscriptionMessageHandler myStandaloneSubscriptionMessageHandler;
+ @Autowired
+ ISubscriptionChannelFactory mySubscriptionDeliveryChannelFactory;
+
+ private static int ourListenerPort;
+ private static RestfulServer ourListenerRestServer;
+ private static Server ourListenerServer;
+ protected static String ourListenerServerBase;
+ protected static List ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
+ protected static List ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
+ protected static List ourContentTypes = Collections.synchronizedList(new ArrayList<>());
+ private static SubscribableChannel ourSubscribableChannel;
+ private List mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
+ private long idCounter = 0;
+
+ @After
+ public void afterUnregisterRestHookListener() {
+ mySubscriptionIds.clear();
+ }
+
+ @Before
+ public void beforeReset() {
+ ourCreatedObservations.clear();
+ ourUpdatedObservations.clear();
+ ourContentTypes.clear();
+ if (ourSubscribableChannel == null) {
+ ourSubscribableChannel = mySubscriptionDeliveryChannelFactory.newDeliveryChannel("test", Subscription.SubscriptionChannelType.RESTHOOK.toCode().toLowerCase());
+ ourSubscribableChannel.subscribe(myStandaloneSubscriptionMessageHandler);
+ }
+ }
+
+ public T sendResource(T theResource) {
+ ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
+ ResourceModifiedJsonMessage message = new ResourceModifiedJsonMessage(msg);
+ ourSubscribableChannel.send(message);
+ return theResource;
+ }
+
+ protected Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException {
+ Subscription subscription = newSubscription(theCriteria, thePayload, theEndpoint);
+
+ return sendResource(subscription);
+ }
+
+ protected Subscription newSubscription(String theCriteria, String thePayload, String theEndpoint) {
+ Subscription subscription = new Subscription();
+ subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
+ subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
+ subscription.setCriteria(theCriteria);
+ ++idCounter;
+ IdType id = new IdType("Subscription", idCounter);
+ subscription.setId(id);
+
+ Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
+ channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
+ channel.setPayload(thePayload);
+ channel.setEndpoint(theEndpoint);
+ subscription.setChannel(channel);
+ return subscription;
+ }
+
+ protected Observation sendObservation(String code, String system) {
+ Observation observation = new Observation();
+ ++idCounter;
+ IdType id = new IdType("Observation", idCounter);
+ observation.setId(id);
+
+ CodeableConcept codeableConcept = new CodeableConcept();
+ observation.setCode(codeableConcept);
+ Coding coding = codeableConcept.addCoding();
+ coding.setCode(code);
+ coding.setSystem(system);
+
+ observation.setStatus(Observation.ObservationStatus.FINAL);
+
+ return sendResource(observation);
+ }
+
+
+ @BeforeClass
+ public static void startListenerServer() throws Exception {
+ ourListenerPort = PortUtil.findFreePort();
+ ourListenerRestServer = new RestfulServer(FhirContext.forDstu3());
+ ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
+
+ ObservationListener obsListener = new ObservationListener();
+ ourListenerRestServer.setResourceProviders(obsListener);
+
+ ourListenerServer = new Server(ourListenerPort);
+
+ ServletContextHandler proxyHandler = new ServletContextHandler();
+ proxyHandler.setContextPath("/");
+
+ ServletHolder servletHolder = new ServletHolder();
+ servletHolder.setServlet(ourListenerRestServer);
+ proxyHandler.addServlet(servletHolder, "/fhir/context/*");
+
+ ourListenerServer.setHandler(proxyHandler);
+ ourListenerServer.start();
+ }
+
+ @AfterClass
+ public static void stopListenerServer() throws Exception {
+ ourListenerServer.stop();
+ }
+
+ public static class ObservationListener implements IResourceProvider {
+
+ @Create
+ public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
+ ourLog.info("Received Listener Create");
+ ourContentTypes.add(theRequest.getHeader(ca.uhn.fhir.rest.api.Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
+ ourCreatedObservations.add(theObservation);
+ return new MethodOutcome(new IdType("Observation/1"), true);
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Observation.class;
+ }
+
+ @Update
+ public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
+ ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
+ ourUpdatedObservations.add(theObservation);
+ ourLog.info("Received Listener Update (now have {} updates)", ourUpdatedObservations.size());
+ return new MethodOutcome(new IdType("Observation/1"), false);
+ }
+
+ }
+
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java
new file mode 100644
index 00000000000..d843316de62
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/standalone/SubscriptionLoaderFhirClientTest.java
@@ -0,0 +1,42 @@
+package ca.uhn.fhir.jpa.subscription.module.standalone;
+
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.server.SimpleBundleProvider;
+import org.hl7.fhir.dstu3.model.Subscription;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class SubscriptionLoaderFhirClientTest extends BaseSubscriptionChannelDstu3Test {
+ private String myCode = "1000000050";
+
+ @Before
+ public void loadSubscriptions() {
+ String payload = "application/fhir+json";
+
+ String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
+ String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml";
+
+
+ List subs = new ArrayList<>();
+ subs.add(newSubscription(criteria1, payload, ourListenerServerBase));
+ subs.add(newSubscription(criteria2, payload, ourListenerServerBase));
+
+ IBundleProvider bundle = new SimpleBundleProvider(new ArrayList<>(subs), "uuid");
+ initSubscriptionLoader(bundle);
+ }
+
+ @Test
+ public void testSubscriptionLoaderFhirClient() throws Exception {
+ sendObservation(myCode, "SNOMED-CT");
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(1, ourUpdatedObservations);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ }
+}
diff --git a/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java
new file mode 100644
index 00000000000..ac8d461455a
--- /dev/null
+++ b/hapi-fhir-jpaserver-subscription/src/test/java/ca/uhn/fhir/jpa/subscription/module/subscriber/SubscriptionCheckingSubscriberTest.java
@@ -0,0 +1,69 @@
+package ca.uhn.fhir.jpa.subscription.module.subscriber;
+
+import ca.uhn.fhir.jpa.subscription.module.standalone.BaseSubscriptionChannelDstu3Test;
+import ca.uhn.fhir.rest.api.Constants;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests copied from jpa.subscription.resthook.RestHookTestDstu3Test
+ */
+public class SubscriptionCheckingSubscriberTest extends BaseSubscriptionChannelDstu3Test {
+ private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionCheckingSubscriberTest.class);
+
+ @Test
+ public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
+ String payload = "application/fhir+json";
+
+ String code = "1000000050";
+ String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
+ String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
+
+ createSubscription(criteria1, payload, ourListenerServerBase);
+ createSubscription(criteria2, payload, ourListenerServerBase);
+
+ sendObservation(code, "SNOMED-CT");
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(1, ourUpdatedObservations);
+ assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
+ }
+
+ @Test
+ public void testRestHookSubscriptionApplicationXmlJson() throws Exception {
+ String payload = "application/fhir+xml";
+
+ String code = "1000000050";
+ String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
+ String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
+
+ createSubscription(criteria1, payload, ourListenerServerBase);
+ createSubscription(criteria2, payload, ourListenerServerBase);
+
+ sendObservation(code, "SNOMED-CT");
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(1, ourUpdatedObservations);
+ assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
+ }
+
+ @Test
+ public void testRestHookSubscriptionWithoutPayload() throws Exception {
+ String payload = "";
+
+ String code = "1000000050";
+ String criteria1 = "Observation?code=SNOMED-CT|" + code;
+ String criteria2 = "Observation?code=SNOMED-CT|" + code + "111";
+
+ createSubscription(criteria1, payload, ourListenerServerBase);
+ createSubscription(criteria2, payload, ourListenerServerBase);
+
+ sendObservation(code, "SNOMED-CT");
+
+ waitForSize(0, ourCreatedObservations);
+ waitForSize(0, ourUpdatedObservations);
+ }
+}
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
index f5d2d3d67ce..76c75078361 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
@@ -21,7 +21,6 @@ import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
-import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.*;
import ca.uhn.hapi.converters.server.VersionedApiConverterInterceptor;
@@ -35,7 +34,6 @@ import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
public class TestRestfulServer extends RestfulServer {
@@ -225,15 +223,6 @@ public class TestRestfulServer extends RestfulServer {
* Spool results to the database
*/
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
-
- /*
- * Load interceptors for the server from Spring
- */
- Collection interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
- for (IServerInterceptor interceptor : interceptorBeans) {
- this.registerInterceptor(interceptor);
- }
-
}
/**
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index fd191539ebe..15298ad10f3 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -180,12 +180,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer TEXT_ENCODE_ELEMENTS = new HashSet(Arrays.asList("Bundle", "*.text", "*.(mandatory)"));
private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap());
+ private enum NarrativeModeEnum {
+ NORMAL, ONLY, SUPPRESS;
+
+ public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) {
+ return valueOf(NarrativeModeEnum.class, theCode.toUpperCase());
+ }
+ }
+
+ /**
+ * Return type for {@link RestfulServerUtils#determineRequestEncodingNoDefault(RequestDetails)}
+ */
+ public static class ResponseEncoding {
+ private final String myContentType;
+ private final EncodingEnum myEncoding;
+ private final Boolean myNonLegacy;
+
+ public ResponseEncoding(FhirContext theCtx, EncodingEnum theEncoding, String theContentType) {
+ super();
+ myEncoding = theEncoding;
+ myContentType = theContentType;
+ if (theContentType != null) {
+ FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
+ if (theContentType.equals(EncodingEnum.JSON_PLAIN_STRING) || theContentType.equals(EncodingEnum.XML_PLAIN_STRING)) {
+ myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU2_1);
+ } else {
+ myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU2_1) && !EncodingEnum.isLegacy(theContentType);
+ }
+ } else {
+ FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
+ if (ctxtEnum.isOlderThan(FhirVersionEnum.DSTU3)) {
+ myNonLegacy = null;
+ } else {
+ myNonLegacy = Boolean.TRUE;
+ }
+ }
+ }
+
+ public String getContentType() {
+ return myContentType;
+ }
+
+ public EncodingEnum getEncoding() {
+ return myEncoding;
+ }
+
+ public String getResourceContentType() {
+ if (Boolean.TRUE.equals(isNonLegacy())) {
+ return getEncoding().getResourceContentTypeNonLegacy();
+ }
+ return getEncoding().getResourceContentType();
+ }
+
+ Boolean isNonLegacy() {
+ return myNonLegacy;
+ }
+ }
+
public static void configureResponseParser(RequestDetails theRequestDetails, IParser parser) {
// Pretty print
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theRequestDetails.getServer(), theRequestDetails);
@@ -272,6 +329,15 @@ public class RestfulServerUtils {
* equally, returns thePrefer.
*/
public static ResponseEncoding determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer) {
+ return determineResponseEncodingNoDefault(theReq, thePrefer, null);
+ }
+
+ /**
+ * Try to determing the response content type, given the request Accept header and
+ * _format parameter. If a value is provided to thePreferContents, we'll
+ * prefer to return that value over the native FHIR value.
+ */
+ public static ResponseEncoding determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer, String thePreferContentType) {
String[] format = theReq.getParameters().get(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
@@ -333,12 +399,12 @@ public class RestfulServerUtils {
ResponseEncoding encoding;
if (endSpaceIndex == -1) {
if (startSpaceIndex == 0) {
- encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken);
+ encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken, thePreferContentType);
} else {
- encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex));
+ encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex), thePreferContentType);
}
} else {
- encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex, endSpaceIndex));
+ encoding = getEncodingForContentType(theReq.getServer().getFhirContext(), strict, nextToken.substring(startSpaceIndex, endSpaceIndex), thePreferContentType);
String remaining = nextToken.substring(endSpaceIndex + 1);
StringTokenizer qualifierTok = new StringTokenizer(remaining, ";");
while (qualifierTok.hasMoreTokens()) {
@@ -476,13 +542,18 @@ public class RestfulServerUtils {
return context;
}
- private static ResponseEncoding getEncodingForContentType(FhirContext theFhirContext, boolean theStrict, String theContentType) {
+ private static ResponseEncoding getEncodingForContentType(FhirContext theFhirContext, boolean theStrict, String theContentType, String thePreferContentType) {
EncodingEnum encoding;
if (theStrict) {
encoding = EncodingEnum.forContentTypeStrict(theContentType);
} else {
encoding = EncodingEnum.forContentType(theContentType);
}
+ if (isNotBlank(thePreferContentType)) {
+ if (thePreferContentType.equals(theContentType)) {
+ return new ResponseEncoding(theFhirContext, encoding, theContentType);
+ }
+ }
if (encoding == null) {
return null;
}
@@ -749,23 +820,6 @@ public class RestfulServerUtils {
return response.sendWriterResponse(theStatusCode, contentType, charset, writer);
}
- public static String createEtag(String theVersionId) {
- return "W/\"" + theVersionId + '"';
- }
-
- public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
- String[] retVal = theRequest.getParameters().get(theParamName);
- if (retVal == null) {
- return null;
- }
- try {
- return Integer.parseInt(retVal[0]);
- } catch (NumberFormatException e) {
- ourLog.debug("Failed to parse {} value '{}': {}", new Object[] {theParamName, retVal[0], e});
- return null;
- }
- }
-
// static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
// String countString = theRequest.getParameter(name);
// Integer count = null;
@@ -779,61 +833,27 @@ public class RestfulServerUtils {
// return count;
// }
+ public static String createEtag(String theVersionId) {
+ return "W/\"" + theVersionId + '"';
+ }
+
+ public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
+ String[] retVal = theRequest.getParameters().get(theParamName);
+ if (retVal == null) {
+ return null;
+ }
+ try {
+ return Integer.parseInt(retVal[0]);
+ } catch (NumberFormatException e) {
+ ourLog.debug("Failed to parse {} value '{}': {}", new Object[]{theParamName, retVal[0], e});
+ return null;
+ }
+ }
+
public static void validateResourceListNotNull(List extends IBaseResource> theResourceList) {
if (theResourceList == null) {
throw new InternalErrorException("IBundleProvider returned a null list of resources - This is not allowed");
}
}
- private enum NarrativeModeEnum {
- NORMAL, ONLY, SUPPRESS;
-
- public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) {
- return valueOf(NarrativeModeEnum.class, theCode.toUpperCase());
- }
- }
-
- /**
- * Return type for {@link RestfulServerUtils#determineRequestEncodingNoDefault(RequestDetails)}
- */
- public static class ResponseEncoding {
- private final EncodingEnum myEncoding;
- private final Boolean myNonLegacy;
-
- public ResponseEncoding(FhirContext theCtx, EncodingEnum theEncoding, String theContentType) {
- super();
- myEncoding = theEncoding;
- if (theContentType != null) {
- FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
- if (theContentType.equals(EncodingEnum.JSON_PLAIN_STRING) || theContentType.equals(EncodingEnum.XML_PLAIN_STRING)) {
- myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU2_1);
- } else {
- myNonLegacy = ctxtEnum.isNewerThan(FhirVersionEnum.DSTU2_1) && !EncodingEnum.isLegacy(theContentType);
- }
- } else {
- FhirVersionEnum ctxtEnum = theCtx.getVersion().getVersion();
- if (ctxtEnum.isOlderThan(FhirVersionEnum.DSTU3)) {
- myNonLegacy = null;
- } else {
- myNonLegacy = Boolean.TRUE;
- }
- }
- }
-
- public EncodingEnum getEncoding() {
- return myEncoding;
- }
-
- public String getResourceContentType() {
- if (Boolean.TRUE.equals(isNonLegacy())) {
- return getEncoding().getResourceContentTypeNonLegacy();
- }
- return getEncoding().getResourceContentType();
- }
-
- public Boolean isNonLegacy() {
- return myNonLegacy;
- }
- }
-
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java
new file mode 100644
index 00000000000..27e70452887
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ServeMediaResourceRawInterceptor.java
@@ -0,0 +1,133 @@
+package ca.uhn.fhir.rest.server.interceptor;
+
+/*-
+ * #%L
+ * HAPI FHIR - Server Framework
+ * %%
+ * Copyright (C) 2014 - 2018 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.server.RestfulServer;
+import ca.uhn.fhir.rest.server.RestfulServerUtils;
+import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+/**
+ * This interceptor allows a client to request that a Media resource be
+ * served as the raw contents of the resource, assuming either:
+ *
+ *
The client explicitly requests the correct content type using the Accept header
+ *
The client explicitly requests raw output by adding the parameter _output=data
+ *
+ */
+public class ServeMediaResourceRawInterceptor extends InterceptorAdapter {
+
+ public static final String MEDIA_CONTENT_CONTENT_TYPE_OPT = "Media.content.contentType";
+
+ private static final Set RESPOND_TO_OPERATION_TYPES;
+
+ static {
+ Set respondToOperationTypes = new HashSet<>();
+ respondToOperationTypes.add(RestOperationTypeEnum.READ);
+ respondToOperationTypes.add(RestOperationTypeEnum.VREAD);
+ RESPOND_TO_OPERATION_TYPES = Collections.unmodifiableSet(respondToOperationTypes);
+ }
+
+ @Override
+ public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
+ if (theResponseObject == null) {
+ return true;
+ }
+
+
+ FhirContext context = theRequestDetails.getFhirContext();
+ String resourceName = context.getResourceDefinition(theResponseObject).getName();
+
+ // Are we serving a FHIR read request on the Media resource type
+ if (!"Media".equals(resourceName) || !RESPOND_TO_OPERATION_TYPES.contains(theRequestDetails.getRestOperationType())) {
+ return true;
+ }
+
+ // What is the content type of the Media resource we're returning?
+ String contentType = null;
+ Optional contentTypeOpt = context.newFluentPath().evaluateFirst(theResponseObject, MEDIA_CONTENT_CONTENT_TYPE_OPT, IPrimitiveType.class);
+ if (contentTypeOpt.isPresent()) {
+ contentType = contentTypeOpt.get().getValueAsString();
+ }
+
+ // What is the data of the Media resource we're returning?
+ IPrimitiveType data = null;
+ Optional dataOpt = context.newFluentPath().evaluateFirst(theResponseObject, "Media.content.data", IPrimitiveType.class);
+ if (dataOpt.isPresent()) {
+ data = dataOpt.get();
+ }
+
+ if (isBlank(contentType) || data == null) {
+ return true;
+ }
+
+ RestfulServerUtils.ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, null, contentType);
+ if (responseEncoding != null) {
+ if (contentType.equals(responseEncoding.getContentType())) {
+ returnRawResponse(theRequestDetails, theServletResponse, contentType, data);
+ return false;
+
+ }
+ }
+
+ String[] outputParam = theRequestDetails.getParameters().get("_output");
+ if (outputParam != null && "data".equals(outputParam[0])) {
+ returnRawResponse(theRequestDetails, theServletResponse, contentType, data);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void returnRawResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, String theContentType, IPrimitiveType theData) {
+ theServletResponse.setStatus(200);
+ if (theRequestDetails.getServer() instanceof RestfulServer) {
+ RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
+ rs.addHeadersToResponse(theServletResponse);
+ }
+
+ theServletResponse.addHeader(Constants.HEADER_CONTENT_TYPE, theContentType);
+
+ // Write the response
+ try {
+ theServletResponse.getOutputStream().write(theData.getValue());
+ theServletResponse.getOutputStream().close();
+ } catch (IOException e) {
+ throw new InternalErrorException(e);
+ }
+ }
+}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
index 66044200232..d5a81fb6e91 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseMethodBinding.java
@@ -84,6 +84,8 @@ public abstract class BaseMethodBinding {
}
}
+ // This allows us to invoke methods on private classes
+ myMethod.setAccessible(true);
}
protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, List> thePreferTypes) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
index 19b8a282812..82cf98d41a5 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java
@@ -19,7 +19,6 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
-import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.ReflectionUtil;
import ca.uhn.fhir.util.UrlUtil;
@@ -57,27 +56,10 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding
*
- * @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
+ * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
@@ -235,6 +270,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
+ v.addExtensionDomains(extensionDomains);
List messages = new ArrayList<>();
@@ -343,7 +379,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private LoadingCache myFetchResourceCache;
private org.hl7.fhir.r4.model.Parameters myExpansionProfile;
- public WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
+ WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext;
myConverter = new VersionConvertor_30_40();
@@ -424,7 +460,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
@Override
- public void cacheResource(org.hl7.fhir.r4.model.Resource res) throws FHIRException {
+ public void cacheResource(org.hl7.fhir.r4.model.Resource res) {
throw new UnsupportedOperationException();
}
@@ -446,7 +482,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
- ValueSet convertedSource = null;
+ ValueSet convertedSource;
try {
convertedSource = VersionConvertor_30_40.convertValueSet(source);
} catch (FHIRException e) {
@@ -470,7 +506,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
@Override
- public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException {
+ public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) {
throw new UnsupportedOperationException();
}
@@ -637,7 +673,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
@Override
- public IResourceValidator newValidator() throws FHIRException {
+ public IResourceValidator newValidator() {
throw new UnsupportedOperationException();
}
@@ -662,7 +698,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
@Override
- public boolean supportsSystem(String system) throws TerminologyServiceException {
+ public boolean supportsSystem(String system) {
return myWrap.supportsSystem(system);
}
@@ -679,6 +715,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override
public ValidationResult validateCode(String system, String code, String display) {
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
+ // TODO: converted code might be null -> NPE
return convertValidationResult(result);
}
@@ -729,6 +766,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e);
}
+ // TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@@ -749,6 +787,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e);
}
+ // TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java
index 0e933a915cc..88f1a24b24e 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.java
@@ -9,7 +9,6 @@ import ca.uhn.fhir.validation.IValidatorModule;
import com.google.gson.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.exceptions.FHIRException;
-import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
@@ -33,6 +32,7 @@ import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -45,6 +45,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private DocumentBuilderFactory myDocBuilderFactory;
private boolean myNoTerminologyChecks;
private StructureDefinition myStructureDefintion;
+ private List extensionDomains = Collections.emptyList();
private IValidationSupport myValidationSupport;
@@ -68,18 +69,52 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport;
}
- private String determineResourceName(Document theDocument) {
- Element root = null;
+ /**
+ * Every element in a resource or data type includes an optional extension child element
+ * which is identified by it's {@code url attribute}. There exists a number of predefined
+ * extension urls or extension domains:
+ *
any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
+ *
any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
+ *
+ * It is possible to extend this list of known extension by defining custom extensions:
+ * Any url which starts which one of the elements in the list of custom extension domains is
+ * considered as known.
+ *
+ * Any unknown extension domain will result in an information message when validating a resource.
+ *
+ */
+ public FhirInstanceValidator setCustomExtensionDomains(List extensionDomains) {
+ this.extensionDomains = extensionDomains;
+ return this;
+ }
+ /**
+ * Every element in a resource or data type includes an optional extension child element
+ * which is identified by it's {@code url attribute}. There exists a number of predefined
+ * extension urls or extension domains:
+ *
any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.
+ *
any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.
+ *
+ * It is possible to extend this list of known extension by defining custom extensions:
+ * Any url which starts which one of the elements in the list of custom extension domains is
+ * considered as known.
+ *
+ * Any unknown extension domain will result in an information message when validating a resource.
+ *
thymeleaf-spring4 (Testpage Overlay) has been replaced with thymeleaf-spring5
+
Commons-Lang3: 3.8 -> 3.8.1
+
Commons-Text: 1.4 -> 1.4
+
Spring Boot: 1.5.6.RELEASE -> 2.1.1.RELEASE
]]>
@@ -27,6 +33,23 @@
DaoConfig (by default it is true). If isEnableInMemorySubscriptionMatching is "false", then all
subscription matching will query the database as before.
+
+ Removed BaseSubscriptionInterceptor and all its subclasses (RestHook, EMail, WebSocket). These are replaced
+ by two new interceptors: SubscriptionActivatingInterceptor that is responsible for activating subscriptions
+ and SubscriptionMatchingInterceptor that is responsible for matching incoming resources against activated
+ subscriptions. Call DaoConfig.addSupportedSubscriptionType(type) to configure which subscription types
+ are supported in your environment. The helper method SubscriptionInterceptorLoader.registerInterceptors()
+ will check if any subscription types are supported, and if so then register both the activating and matching
+ interceptors. See https://github.com/jamesagnew/hapi-fhir/wiki/Proposed-Subscription-Design-Change for more
+ details.
+
+
+ Added support for matching subscriptions in a separate server from the REST Server. To do this, run the
+ SubscriptionActivatingInterceptor on the REST server and the SubscriptionMatchingInterceptor in the
+ standalone server. Classes required to support running a standalone subscription server are in the
+ ca.uhn.fhir.jpa.subscription.module.standalone package. These classes are excluded by default from
+ the JPA ApplicationContext (that package is explicitly filtered out in the BaseConfig.java @ComponentScan).
+
Changed behaviour of FHIR Server to reject subscriptions with invalid criteria. If a Subscription
is submitted with invalid criteria, the server returns HTTP 422 "Unprocessable Entity" and the
@@ -100,9 +123,11 @@
or authorizing the contents of the response.
+ JPA Migrator tool enhancements:
An invalid SQL syntax issue has been fixed when running the CLI JPA Migrator tool against
Oracle or SQL Server. In addition, when using the "Dry Run" option, all generated SQL
- statements will be logged at the end of the run.
+ statements will be logged at the end of the run. Also, a case sensitivity issue when running against
+ some Postgres databases has been corrected.
In the JPA server, when performing a chained reference search on a search parameter with
@@ -134,6 +159,68 @@
An issue was corrected with the JPA reindexer, where String index columns do not always
get reindexed if they did not have an identity hash value in the HASH_IDENTITY column.
+
+ Plain Server ResourceProvider classes are no longer required to be public classes. This
+ limitation has always been enforced, but did not actually serve any real purpose so it
+ has been removed.
+
+
+ A new interceptor called ServeMediaResourceRawInterceptor has been added. This interceptor
+ causes Media resources to be served as raw content if the client explicitly requests
+ the correct content type cia the Accept header.
+
+
+ A new configuration item has been added to the FhirInstanceValidator that
+ allows you to specify additional "known extension domains", meaning
+ domains in which the validator will not complain about when it
+ encounters new extensions. Thanks to Heinz-Dieter Conradi for the
+ pull request!
+
+
+ Under some circumstances, when a custom search parameter was added to the JPA server
+ resources could start reindexing before the new search parameter had been saved, meaning that
+ it was not applied to all resources. This has been corrected.
+
+
+ In example-projects/README.md and hapi-fhir-jpaserver-example/README.md, incidate that these examples projects
+ are no longer maintained. The README.md points users to a starter project they should use for examples.
+
+
+ Replaced use of BeanFactory with custom factory classes that Spring @Lookup the @Scope("prototype") beans
+ (e.g. SearchBuilderFactory).
+
+
+ Moved e-mail from address configuration from EmailInterceptor (which doesn't exist any more) to DaoConfig.
+
+
+ Added 3 interfaces for services required by the standalone subscription server. The standalone subscription
+ server doesn't have access to a database and so needs to get its resources using a FhirClient. Thus
+ for each of these interfaces, there are two implementations: a Dao implementaiton and a FhirClient
+ implementation. The interfaces thus introduced are ISubscriptionProvider (used to load subscriptions
+ into the SubscriptionRegistry), the IResourceProvider (used to get the latest version of a resource
+ if the "get latest version" flag is set on the subscription) and ISearchParamProvider used to load
+ custom search parameters.
+
+
+ Separated active subscription cache from the interceptors into a new Spring component called the
+ SubscriptionRegistry. This component maintains a cache of ActiveSubscriptions. An ActiveSubscription
+ contains the subscription, it's delivery channel, and a list of delivery handlers.
+
+
+ Introduced a Spring factory interfaces called ISubscriptionDeliveryChannelFactory and
+ ISubscriptionDeliveryHandlerFactory that are used to create delivery channels and handlers. By default,
+ HAPI FHIR ships with a LinkedBlockingQueue implementation of the delivery channel factory. If a different
+ type of channel factory is required, add it to your application context and mark it as @Primary.
+
+
+ When using the HL7.org DSTU2 structures, a QuestionnaireResponse with a
+ value of type reference would fail to parse. Thanks to David Gileadi for
+ the pull request!
+
+
+ FHIR Servers now support the HTTP HEAD method for FHIR read operations. Thanks
+ to GitHub user Cory00 for the pull request!
+