Merge branch 'master' into test-openjdk-11

This commit is contained in:
jamesagnew 2018-12-23 14:19:59 -05:00
commit 4599a10180
197 changed files with 5559 additions and 2641 deletions

View File

@ -8,7 +8,7 @@ dist: trusty
language: java language: java
jdk: jdk:
- oraclejdk11 - openjdk11
env: env:
global: global:
- MAVEN_OPTS="-Xmx10244M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" - MAVEN_OPTS="-Xmx10244M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC"

View File

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

View File

@ -10,11 +10,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass; import org.junit.*;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.Ignore;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -26,7 +22,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Scanner; import java.util.Scanner;
// FIXME KHS // TODO Remove @Ignore once Chris Schuler has fixed the external jar this project depends on
@Ignore @Ignore
public class CdsExampleTests { public class CdsExampleTests {
private static IGenericClient ourClient; private static IGenericClient ourClient;

View File

@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; 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.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; 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 { public class JpaServerDemo extends RestfulServer {
@ -129,12 +126,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); 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<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
for (IServerInterceptor interceptor : interceptorBeans) { subscriptionInterceptorLoader.registerInterceptors();
this.registerInterceptor(interceptor);
}
/* /*
* If you are hosting this server at a specific DNS name, the server will try to * If you are hosting this server at a specific DNS name, the server will try to

View File

@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; 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.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; 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 { public class JpaServerDemoDstu2 extends RestfulServer {
@ -129,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); 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<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
for (IServerInterceptor interceptor : interceptorBeans) { subscriptionInterceptorLoader.registerInterceptors();
this.registerInterceptor(interceptor);
}
/* /*
* If you are hosting this server at a specific DNS name, the server will try to * If you are hosting this server at a specific DNS name, the server will try to

View File

@ -1,14 +1,5 @@
package ca.uhn.fhir.jpa.demo; 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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao; 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.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; 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.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; 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 { public class JpaServerDemo extends RestfulServer {
@ -96,12 +93,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); 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<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
for (IServerInterceptor interceptor : interceptorBeans) { subscriptionInterceptorLoader.registerInterceptors();
this.registerInterceptor(interceptor);
}
/* /*
* If you are hosting this server at a specific DNS name, the server will try to * If you are hosting this server at a specific DNS name, the server will try to

View File

@ -99,6 +99,10 @@
<groupId>javax.mail</groupId> <groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId> <artifactId>javax.mail-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.helger</groupId> <groupId>com.helger</groupId>
<artifactId>ph-schematron</artifactId> <artifactId>ph-schematron</artifactId>

View File

@ -48,6 +48,12 @@
<groupId>com.helger</groupId> <groupId>com.helger</groupId>
<artifactId>ph-schematron</artifactId> <artifactId>ph-schematron</artifactId>
<optional>true</optional> <optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-core</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.helger</groupId> <groupId>com.helger</groupId>

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.fluentpath;
*/ */
import java.util.List; import java.util.List;
import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
@ -36,6 +37,15 @@ public interface IFluentPath {
*/ */
<T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> theReturnType); <T extends IBase> List<T> evaluate(IBase theInput, String thePath, Class<T> 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)
*/
<T extends IBase> Optional<T> evaluateFirst(IBase theInput, String thePath, Class<T> theReturnType);
} }

View File

@ -53,8 +53,8 @@ public class FhirTerser {
if (theChildDefinition == null) if (theChildDefinition == null)
return null; return null;
if (theCurrentList == null || theCurrentList.isEmpty()) if (theCurrentList == null || theCurrentList.isEmpty())
return new ArrayList<String>(Arrays.asList(theChildDefinition.getElementName())); return new ArrayList<>(Arrays.asList(theChildDefinition.getElementName()));
List<String> newList = new ArrayList<String>(theCurrentList); List<String> newList = new ArrayList<>(theCurrentList);
newList.add(theChildDefinition.getElementName()); newList.add(theChildDefinition.getElementName());
return newList; return newList;
} }

View File

@ -218,13 +218,17 @@
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
</dependency> </dependency>
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-core</artifactId>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-impl</artifactId>-->
<!--</dependency>-->
<dependency> <dependency>
<groupId>com.sun.xml.bind</groupId> <groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-core</artifactId> <artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency> </dependency>

View File

@ -256,27 +256,30 @@ public abstract class BaseApp {
System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff()); System.err.println("" + ansi().fg(Ansi.Color.WHITE).boldOff());
logCommandUsageNoHeader(command); logCommandUsageNoHeader(command);
runCleanupHookAndUnregister(); runCleanupHookAndUnregister();
System.exit(1); exitDueToException(e);
} catch (CommandFailureException e) { } catch (CommandFailureException e) {
ourLog.error(e.getMessage()); ourLog.error(e.getMessage());
runCleanupHookAndUnregister(); runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) { exitDueToException(e);
throw e;
} else {
System.exit(1);
}
} catch (Throwable t) { } catch (Throwable t) {
ourLog.error("Error during execution: ", t); ourLog.error("Error during execution: ", t);
runCleanupHookAndUnregister(); runCleanupHookAndUnregister();
if ("true".equals(System.getProperty("test"))) { exitDueToException(new CommandFailureException("Error: " + t.toString(), t));
throw new CommandFailureException("Error: " + t.toString(), t);
} else {
System.exit(1);
}
} }
} }
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() { private void runCleanupHookAndUnregister() {
if (myShutdownHookHasNotRun) { if (myShutdownHookHasNotRun) {
Runtime.getRuntime().removeShutdownHook(myShutdownHook); Runtime.getRuntime().removeShutdownHook(myShutdownHook);

View File

@ -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.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; 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.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; 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.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; 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.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
public class JpaServerDemo extends RestfulServer { public class JpaServerDemo extends RestfulServer {
@ -143,19 +142,14 @@ public class JpaServerDemo extends RestfulServer {
CorsInterceptor corsInterceptor = new CorsInterceptor(); CorsInterceptor corsInterceptor = new CorsInterceptor();
registerInterceptor(corsInterceptor); registerInterceptor(corsInterceptor);
/*
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
*/
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
for (IServerInterceptor interceptor : interceptorBeans) {
this.registerInterceptor(interceptor);
}
DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class); DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class);
daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs()); daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs());
daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity()); daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity());
daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity()); daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity());
daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis()); daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis());
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
subscriptionInterceptorLoader.registerInterceptors();
} }
} }

View File

@ -231,14 +231,14 @@
<groupId>javax.xml.bind</groupId> <groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-api</artifactId>
</dependency> </dependency>
<dependency> <!--<dependency>
<groupId>com.sun.xml.bind</groupId> <groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId> <artifactId>jaxb-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.sun.xml.bind</groupId> <groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId> <artifactId>jaxb-impl</artifactId>
</dependency> </dependency>-->
<!-- Test Database --> <!-- Test Database -->
<dependency> <dependency>
@ -360,18 +360,10 @@
<artifactId>xml-apis</artifactId> <artifactId>xml-apis</artifactId>
<groupId>xml-apis</groupId> <groupId>xml-apis</groupId>
</exclusion> </exclusion>
<exclusion> <!--<exclusion>-->
<groupId>org.jboss.spec.javax.transaction</groupId> <!--<groupId>org.jboss.spec.javax.transaction</groupId>-->
<artifactId>jboss-transaction-api_1.2_spec</artifactId> <!--<artifactId>jboss-transaction-api_1.2_spec</artifactId>-->
</exclusion> <!--</exclusion>-->
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
</exclusion>
<exclusion>
<groupId>javax.activation</groupId>
<artifactId>javax.activation-api</artifactId>
</exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
@ -409,9 +401,14 @@
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.transaction</groupId> <groupId>com.sun.activation</groupId>
<artifactId>javax.transaction-api</artifactId> <artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency> </dependency>
<!--<dependency>-->
<!--<groupId>javax.transaction</groupId>-->
<!--<artifactId>javax.transaction-api</artifactId>-->
<!--</dependency>-->
<dependency> <dependency>
<groupId>javax.mail</groupId> <groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId> <artifactId>javax.mail-api</artifactId>
@ -428,10 +425,10 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <!--<dependency>-->
<groupId>com.sun.activation</groupId> <!--<groupId>com.sun.activation</groupId>-->
<artifactId>javax.activation</artifactId> <!--<artifactId>javax.activation</artifactId>-->
</dependency> <!--</dependency>-->
<!--<dependency> <!--<dependency>
<groupId>javax.validation</groupId> <groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId> <artifactId>validation-api</artifactId>
@ -596,20 +593,20 @@
as JDK9 no longer includes them by default as JDK9 no longer includes them by default
--> -->
<dependency> <dependency>
<groupId>javax.xml.bind</groupId> <groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-api</artifactId> <artifactId>jaxb-runtime</artifactId>
<version>${jaxb_api_version}</version> <version>${jaxb_runtime_version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>${jaxb_core_version}</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>${jaxb_core_version}</version>
</dependency> </dependency>
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-core</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--</dependency>-->
<!--<dependency>-->
<!--<groupId>com.sun.xml.bind</groupId>-->
<!--<artifactId>jaxb-impl</artifactId>-->
<!--<version>${jaxb_core_version}</version>-->
<!--</dependency>-->
</dependencies> </dependencies>
</plugin> </plugin>
<plugin> <plugin>

View File

@ -2,18 +2,18 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.HapiLocalizer; 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.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl; import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl; import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.config.BaseSubscriptionConfig; import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor; import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor; 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.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -59,7 +59,8 @@ import javax.annotation.Nonnull;
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={ @ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class), @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 { public abstract class BaseConfig implements SchedulingConfigurer {
@ -132,34 +133,29 @@ public abstract class BaseConfig implements SchedulingConfigurer {
} }
@Bean @Bean
protected ISearchParamProvider searchParamProvider() { public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() {
return new DatabaseSearchParamProvider(); return new InMemorySubscriptionMatcher();
}
@Bean
public DaoSubscriptionMatcher daoSubscriptionMatcher() {
return new DaoSubscriptionMatcher();
} }
/** /**
* Note: If you're going to use this, you need to provide a bean * Create a @Primary @Bean if you need a different implementation
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
* in your own Spring config
*/ */
@Bean @Bean
@Lazy public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() {
public SubscriptionEmailInterceptor subscriptionEmailInterceptor() { return new BlockingQueueSubscriptionChannelFactory();
return new SubscriptionEmailInterceptor();
} }
@Bean @Bean
@Lazy @Primary
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() { public ISubscriptionMatcher subscriptionMatcherCompositeInMemoryDatabase() {
return new SubscriptionRestHookInterceptor(); return new CompositeInMemoryDaoSubscriptionMatcher(daoSubscriptionMatcher(), inMemorySubscriptionMatcher());
} }
@Bean
@Lazy
public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
return new SubscriptionWebsocketInterceptor();
}
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer())); theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity"); theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");

View File

@ -116,7 +116,7 @@ public class BaseDstu2Config extends BaseConfig {
@Bean @Bean
public ISearchParamRegistry searchParamRegistry() { public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu2(searchParamProvider()); return new SearchParamRegistryDstu2();
} }
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME) @Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)

View File

@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.config;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler; import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionWebsocketHandler;
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;

View File

@ -123,7 +123,7 @@ public class BaseDstu3Config extends BaseConfig {
@Bean @Bean
public ISearchParamRegistry searchParamRegistry() { public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryDstu3(searchParamProvider()); return new SearchParamRegistryDstu3();
} }
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME) @Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)

View File

@ -138,7 +138,7 @@ public class BaseR4Config extends BaseConfig {
@Bean @Bean
public ISearchParamRegistry searchParamRegistry() { public ISearchParamRegistry searchParamRegistry() {
return new SearchParamRegistryR4(searchParamProvider()); return new SearchParamRegistryR4();
} }
@Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME) @Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME)

View File

@ -2,16 +2,16 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.context.*; import ca.uhn.fhir.context.*;
import ca.uhn.fhir.jpa.dao.data.*; import ca.uhn.fhir.jpa.dao.data.*;
import ca.uhn.fhir.jpa.dao.index.*; import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
import ca.uhn.fhir.jpa.model.entity.*; 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.entity.*;
import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams; 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.LogicalReferenceHelper;
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; 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.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; 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.instance.model.api.*;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationContextAware;
@ -78,8 +77,6 @@ import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.xml.stream.events.Characters; import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;
import java.io.CharArrayWriter;
import java.text.Normalizer;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -174,23 +171,17 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
@Autowired @Autowired
private ISearchDao mySearchDao; private ISearchDao mySearchDao;
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private ISearchParamPresenceSvc mySearchParamPresenceSvc; private ISearchParamPresenceSvc mySearchParamPresenceSvc;
//@Autowired //@Autowired
//private ISearchResultDao mySearchResultDao; //private ISearchResultDao mySearchResultDao;
@Autowired @Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@Autowired
private BeanFactory beanFactory;
@Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
@Autowired @Autowired
private SearchParamExtractorService mySearchParamExtractorService;
@Autowired
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor; private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
@Autowired @Autowired
private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer; private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@Autowired
private SearchBuilderFactory mySearchBuilderFactory;
private ApplicationContext myApplicationContext; private ApplicationContext myApplicationContext;
@ -752,9 +743,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId); return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId);
} }
@Override // TODO KHS inject a searchBuilderFactory into callers of this method and delete this method
public SearchBuilder newSearchBuilder() { public SearchBuilder newSearchBuilder() {
return beanFactory.getBean(SearchBuilder.class, this); return mySearchBuilderFactory.newSearchBuilder(this);
} }
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) { public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
@ -1412,7 +1403,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
* Indexing * Indexing
*/ */
if (thePerformIndexing) { if (thePerformIndexing) {
myDatabaseSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams); myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams);
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams); mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams);
} }

View File

@ -24,13 +24,11 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; 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.dao.r4.MatchResourceUrlService;
import ca.uhn.fhir.jpa.model.entity.*; import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; 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.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.DeleteConflict; import ca.uhn.fhir.jpa.util.DeleteConflict;
import ca.uhn.fhir.jpa.util.ExpungeOptions; import ca.uhn.fhir.jpa.util.ExpungeOptions;
@ -73,17 +71,19 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> { public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
@Autowired @Autowired
protected PlatformTransactionManager myPlatformTransactionManager; protected PlatformTransactionManager myPlatformTransactionManager;
@Autowired(required = false) @Autowired(required = false)
protected IFulltextSearchSvc mySearchDao; protected IFulltextSearchSvc mySearchDao;
@Autowired @Autowired
protected DaoConfig myDaoConfig; protected DaoConfig myDaoConfig;
@Autowired
private MatchResourceUrlService myMatchResourceUrlService;
private String myResourceName; private String myResourceName;
private Class<T> myResourceType; private Class<T> myResourceType;
private String mySecondaryPrimaryKeyParamName; private String mySecondaryPrimaryKeyParamName;
@Autowired
private MatchResourceUrlService myMatchResourceUrlService;
@Override @Override
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) { public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {

View File

@ -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.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry; import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DateUtils; import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.Subscription;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -112,7 +112,7 @@ public class DaoConfig {
* update setter javadoc if default changes * update setter javadoc if default changes
*/ */
private boolean myIndexContainedResources = true; private boolean myIndexContainedResources = true;
private List<IServerInterceptor> myInterceptors; private List<IServerInterceptor> myInterceptors = new ArrayList<>();
/** /**
* update setter javadoc if default changes * update setter javadoc if default changes
*/ */
@ -484,14 +484,26 @@ public class DaoConfig {
* Returns the interceptors which will be notified of operations. * Returns the interceptors which will be notified of operations.
* *
* @see #setInterceptors(List) * @see #setInterceptors(List)
* @deprecated Marked as deprecated as of HAPI 3.7.0. Use {@link #registerInterceptor} or {@link #unregisterInterceptor}instead.
*/ */
@Deprecated
public List<IServerInterceptor> getInterceptors() { public List<IServerInterceptor> getInterceptors() {
if (myInterceptors == null) {
myInterceptors = new ArrayList<>();
}
return myInterceptors; 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. * This may be used to optionally register server interceptors directly against the DAOs.
*/ */
@ -1462,6 +1474,47 @@ public class DaoConfig {
myModelConfig.setDefaultSearchParamsCanBeOverridden(theDefaultSearchParamsCanBeOverridden); 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<Subscription.SubscriptionChannelType> 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 { public enum IndexEnabledEnum {
ENABLED, ENABLED,

View File

@ -20,17 +20,19 @@ package ca.uhn.fhir.jpa.dao;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; 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.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
public class DatabaseSearchParamProvider implements ISearchParamProvider { @Service
public class DaoSearchParamProvider implements ISearchParamProvider {
@Autowired @Autowired
private PlatformTransactionManager myTxManager; private PlatformTransactionManager myTxManager;
@Autowired @Autowired

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription.websocket; package ca.uhn.fhir.jpa.dao;
/*- /*-
* #%L * #%L
@ -20,24 +20,11 @@ package ca.uhn.fhir.jpa.subscription.websocket;
* #L% * #L%
*/ */
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; import org.springframework.beans.factory.annotation.Lookup;
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription; import org.springframework.stereotype.Service;
import org.hl7.fhir.r4.model.Subscription;
import org.springframework.messaging.MessageHandler;
import java.util.Optional;
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
@Override
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
return Optional.empty();
}
@Override
public Subscription.SubscriptionChannelType getChannelType() {
return Subscription.SubscriptionChannelType.WEBSOCKET;
}
@Service
public abstract class SearchBuilderFactory {
@Lookup
public abstract SearchBuilder newSearchBuilder(BaseHapiFhirDao theBaseHapiFhirResourceDao);
} }

View File

@ -41,8 +41,8 @@ import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType; import javax.persistence.PersistenceContextType;
@Service @Service
public class DatabaseResourceLinkResolver implements IResourceLinkResolver { public class DaoResourceLinkResolver implements IResourceLinkResolver {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DatabaseResourceLinkResolver.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class);
@Autowired @Autowired
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;

View File

@ -35,7 +35,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
@Service @Service
public class DatabaseSearchParamSynchronizer { public class DaoSearchParamSynchronizer {
@Autowired @Autowired
private DaoConfig myDaoConfig; private DaoConfig myDaoConfig;

View File

@ -77,9 +77,9 @@ public class SearchParamWithInlineReferencesExtractor {
@Autowired @Autowired
ResourceLinkExtractor myResourceLinkExtractor; ResourceLinkExtractor myResourceLinkExtractor;
@Autowired @Autowired
DatabaseResourceLinkResolver myDatabaseResourceLinkResolver; DaoResourceLinkResolver myDaoResourceLinkResolver;
@Autowired @Autowired
DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer; DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
@Autowired @Autowired
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao; private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
@ -99,7 +99,7 @@ public class SearchParamWithInlineReferencesExtractor {
extractInlineReferences(theResource); 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 * 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 // Store composite string uniques
if (myDaoConfig.isUniqueIndexesEnabled()) { 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); ourLog.debug("Removing unique index: {}", next);
myEntityManager.remove(next); myEntityManager.remove(next);
theEntity.getParamsCompositeStringUnique().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()) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
if (existing != null) { if (existing != null) {

View File

@ -28,7 +28,9 @@ import ca.uhn.fhir.rest.server.IPagingProvider;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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 { public class DatabaseBackedPagingProvider extends BasePagingProvider implements IPagingProvider {
@Autowired @Autowired

View File

@ -58,4 +58,6 @@ public interface IResourceReindexingSvc {
* to be used by unit tests. * to be used by unit tests.
*/ */
void cancelAndPurgeAllJobs(); void cancelAndPurgeAllJobs();
int countReindexJobs();
} }

View File

@ -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.IResourceHistoryTableDao;
import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao; import ca.uhn.fhir.jpa.dao.data.IResourceReindexJobDao;
import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; 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.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ForcedId;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -98,6 +98,8 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
private FhirContext myContext; private FhirContext myContext;
@PersistenceContext(type = PersistenceContextType.TRANSACTION) @PersistenceContext(type = PersistenceContextType.TRANSACTION)
private EntityManager myEntityManager; private EntityManager myEntityManager;
@Autowired
private ISearchParamRegistry mySearchParamRegistry;
@VisibleForTesting @VisibleForTesting
void setReindexJobDaoForUnitTest(IResourceReindexJobDao theReindexJobDao) { void setReindexJobDaoForUnitTest(IResourceReindexJobDao theReindexJobDao) {
@ -186,7 +188,6 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
runReindexingPass(); runReindexingPass();
} }
@Override @Override
@Transactional(Transactional.TxType.NEVER) @Transactional(Transactional.TxType.NEVER)
public Integer runReindexingPass() { public Integer runReindexingPass() {
@ -203,7 +204,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return null; return null;
} }
private Integer doReindexingPassInsideLock() { private int doReindexingPassInsideLock() {
expungeJobsMarkedAsDeleted(); expungeJobsMarkedAsDeleted();
return runReindexJobs(); return runReindexJobs();
} }
@ -233,13 +234,13 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
} }
private int runReindexJobs() { private int runReindexJobs() {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false)); Collection<ResourceReindexJobEntity> jobs = getResourceReindexJobEntities();
assert jobs != null;
if (jobs.size() > 0) { if (jobs.size() > 0) {
ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs); ourLog.info("Running {} reindex jobs: {}", jobs.size(), jobs);
} else { } else {
ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs); ourLog.debug("Running {} reindex jobs: {}", jobs.size(), jobs);
return 0;
} }
int count = 0; int count = 0;
@ -255,6 +256,17 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
return count; return count;
} }
@Override
public int countReindexJobs() {
return getResourceReindexJobEntities().size();
}
private Collection<ResourceReindexJobEntity> getResourceReindexJobEntities() {
Collection<ResourceReindexJobEntity> jobs = myTxTemplate.execute(t -> myReindexJobDao.findAll(PageRequest.of(0, 10), false));
assert jobs != null;
return jobs;
}
private void markJobAsDeleted(ResourceReindexJobEntity theJob) { private void markJobAsDeleted(ResourceReindexJobEntity theJob) {
ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId()); ourLog.info("Marking reindexing job ID[{}] as deleted", theJob.getId());
myTxTemplate.execute(t -> { 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) { private int runReindexJob(ResourceReindexJobEntity theJob) {
if (theJob.getSuspendedUntil() != null) { if (theJob.getSuspendedUntil() != null) {
if (theJob.getSuspendedUntil().getTime() > System.currentTimeMillis()) { if (theJob.getSuspendedUntil().getTime() > System.currentTimeMillis()) {
@ -274,6 +291,16 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
StopWatch sw = new StopWatch(); StopWatch sw = new StopWatch();
AtomicInteger counter = new AtomicInteger(); 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 // Calculate range
Date low = theJob.getThresholdLow() != null ? theJob.getThresholdLow() : BEGINNING_OF_TIME; Date low = theJob.getThresholdLow() != null ? theJob.getThresholdLow() : BEGINNING_OF_TIME;
Date high = theJob.getThresholdHigh(); Date high = theJob.getThresholdHigh();
@ -461,7 +488,7 @@ public class ResourceReindexingSvcImpl implements IResourceReindexingSvc {
IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceTable.getResourceType()); IFhirResourceDao<?> dao = myDaoRegistry.getResourceDao(resourceTable.getResourceType());
long expectedVersion = resourceTable.getVersion(); 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) { if (resource == null) {
throw new InternalErrorException("Could not find resource version " + resourceTable.getIdDt().toUnqualified().getValue() + " in database"); throw new InternalErrorException("Could not find resource version " + resourceTable.getIdDt().toUnqualified().getValue() + " in database");
} }

View File

@ -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<S extends IBaseResource> 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<String, SubscribableChannel> myDeliveryChannel;
private ExecutorService myProcessingExecutor;
private int myExecutorThreadCount;
private SubscriptionActivatingSubscriber mySubscriptionActivatingSubscriber;
private MessageHandler mySubscriptionCheckingSubscriber;
private ConcurrentHashMap<String, CanonicalSubscription> myIdToSubscription = new ConcurrentHashMap<>();
private ConcurrentHashMap<String, SubscribableChannel> mySubscribableChannel = new ConcurrentHashMap<>();
private Multimap<String, MessageHandler> myIdToDeliveryHandler = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
private ThreadPoolExecutor myDeliveryExecutor;
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
@Autowired
private FhirContext myCtx;
@Autowired(required = false)
@Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> 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<org.hl7.fhir.r4.model.Extension> 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<Runnable> 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<MessageHandler> 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<String, CanonicalSubscription> getIdToSubscription() {
return Collections.unmodifiableMap(myIdToSubscription);
}
public SubscribableChannel getProcessingChannel() {
return myProcessingChannel;
}
public void setProcessingChannel(SubscribableChannel theProcessingChannel) {
myProcessingChannel = theProcessingChannel;
}
public List<CanonicalSubscription> 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<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
Set<String> 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<MessageHandler> 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<String> 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<IFhirResourceDao> 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Subscription.SubscriptionChannelType> supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes();
if (!supportedSubscriptionTypes.isEmpty()) {
myDaoConfig.registerInterceptor(mySubscriptionActivatingInterceptor);
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
}
}
@VisibleForTesting
public void unregisterInterceptorsForUnitTest() {
myDaoConfig.unregisterInterceptor(mySubscriptionActivatingInterceptor);
myDaoConfig.unregisterInterceptor(mySubscriptionMatcherInterceptor);
}
}

View File

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

View File

@ -22,13 +22,16 @@ package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; 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.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; 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.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl; import ca.uhn.fhir.jpa.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.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.api.CacheControlDirective; 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.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.util.ParametersUtil; import ca.uhn.fhir.util.ParametersUtil;
import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.ValidateUtil; import ca.uhn.fhir.util.ValidateUtil;
@ -71,26 +75,31 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
@Service @Service
public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware { public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware {
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
public static final int DEFAULT_MAX_SUBMIT = 10000; public static final int DEFAULT_MAX_SUBMIT = 10000;
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
@Autowired @Autowired
private FhirContext myFhirContext; private FhirContext myFhirContext;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; private DaoRegistry myDaoRegistry;
private List<BaseSubscriptionInterceptor<?>> mySubscriptionInterceptorList; @Autowired
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT; private DaoConfig myDaoConfig;
@Autowired @Autowired
private ISearchCoordinatorSvc mySearchCoordinatorSvc; private ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Autowired @Autowired
private MatchUrlService myMatchUrlService; private MatchUrlService myMatchUrlService;
@Autowired
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
private ApplicationContext myAppCtx; private ApplicationContext myAppCtx;
private ExecutorService myExecutorService; private ExecutorService myExecutorService;
@Override @Override
public IBaseParameters triggerSubscription(List<UriParam> theResourceIds, List<StringParam> theSearchUrls, @IdParam IIdType theSubscriptionId) { public IBaseParameters triggerSubscription(List<UriParam> theResourceIds, List<StringParam> theSearchUrls, @IdParam IIdType theSubscriptionId) {
if (mySubscriptionInterceptorList.isEmpty()) { if (myDaoConfig.getSupportedSubscriptionTypes().isEmpty()) {
throw new PreconditionFailedException("Subscription processing not active on this server"); 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); ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId);
ResourceModifiedMessage msg = new ResourceModifiedMessage(); ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResourceToTrigger, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
msg.setId(theResourceToTrigger.getIdElement());
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE);
msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue()); msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue());
msg.setNewPayload(myFhirContext, theResourceToTrigger);
return myExecutorService.submit(() -> { return myExecutorService.submit(() -> {
for (int i = 0; ; i++) { for (int i = 0; ; i++) {
try { try {
for (BaseSubscriptionInterceptor<?> next : mySubscriptionInterceptorList) { mySubscriptionMatcherInterceptor.submitResourceModified(msg);
next.submitResourceModified(msg);
}
break; break;
} catch (Exception e) { } catch (Exception e) {
if (i >= 3) { if (i >= 3) {
@ -347,13 +351,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@PostConstruct @PostConstruct
public void start() { public void start() {
mySubscriptionInterceptorList = ObjectUtils.defaultIfNull(mySubscriptionInterceptorList, Collections.emptyList());
mySubscriptionInterceptorList = new ArrayList<>();
Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values();
Collection<BaseSubscriptionInterceptor<?>> values = (Collection<BaseSubscriptionInterceptor<?>>) values1;
mySubscriptionInterceptorList.addAll(values);
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000); LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder() BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
.namingPattern("SubscriptionTriggering-%d") .namingPattern("SubscriptionTriggering-%d")

View File

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

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription.matcher; package ca.uhn.fhir.jpa.subscription.dbmatcher;
/*- /*-
* #%L * #%L
@ -21,34 +21,38 @@ package ca.uhn.fhir.jpa.subscription.matcher;
*/ */
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMatcher {
public class SubscriptionMatcherCompositeInMemoryDatabase implements ISubscriptionMatcher { private Logger ourLog = LoggerFactory.getLogger(CompositeInMemoryDaoSubscriptionMatcher.class);
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherCompositeInMemoryDatabase.class);
@Autowired private final DaoSubscriptionMatcher myDaoSubscriptionMatcher;
SubscriptionMatcherDatabase mySubscriptionMatcherDatabase; private final InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
@Autowired
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
@Autowired @Autowired
DaoConfig myDaoConfig; DaoConfig myDaoConfig;
public CompositeInMemoryDaoSubscriptionMatcher(DaoSubscriptionMatcher theDaoSubscriptionMatcher, InMemorySubscriptionMatcher theInMemorySubscriptionMatcher) {
myDaoSubscriptionMatcher = theDaoSubscriptionMatcher;
myInMemorySubscriptionMatcher = theInMemorySubscriptionMatcher;
}
@Override @Override
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) { public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
SubscriptionMatchResult result; SubscriptionMatchResult result;
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) { if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
result = mySubscriptionMatcherInMemory.match(criteria, msg); result = myInMemorySubscriptionMatcher.match(criteria, msg);
if (!result.supported()) { if (!result.supported()) {
ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason()); ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason());
result = mySubscriptionMatcherDatabase.match(criteria, msg); result = myDaoSubscriptionMatcher.match(criteria, msg);
} }
} else { } else {
result = mySubscriptionMatcherDatabase.match(criteria, msg); result = myDaoSubscriptionMatcher.match(criteria, msg);
} }
return result; return result;
} }

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.jpa.subscription.matcher; package ca.uhn.fhir.jpa.subscription.dbmatcher;
/*- /*-
* #%L * #%L
@ -24,10 +24,12 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails; import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.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.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.RequestDetails;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
@Lazy private Logger ourLog = LoggerFactory.getLogger(DaoSubscriptionMatcher.class);
public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class);
@Autowired @Autowired
private FhirContext myCtx; private FhirContext myCtx;

View File

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

View File

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

View File

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

View File

@ -23,51 +23,6 @@ package ca.uhn.fhir.jpa.util;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
public class JpaConstants { public class JpaConstants {
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
/**
* <p>
* This extension should be of type <code>string</code> and should be
* placed on the <code>Subscription.channel</code> element
* </p>
*/
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.
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
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.
* <p>
* Note that if the resource is now deleted, this may cause
* the delivery to be cancelled altogether.
* </p>
*
* <p>
* This extension should be of type <code>boolean</code> and should be
* placed on the <code>Subscription.channel</code> element.
* </p>
*/
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
/** /**
* Operation name for the $expunge operation * Operation name for the $expunge operation
*/ */

View File

@ -0,0 +1,50 @@
package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import java.util.concurrent.CountDownLatch;
public class StoppableSubscriptionDeliveringRestHookSubscriber extends SubscriptionDeliveringRestHookSubscriber {
private static final Logger ourLog = LoggerFactory.getLogger(StoppableSubscriptionDeliveringRestHookSubscriber.class);
private boolean myPauseEveryMessage = false;
private CountDownLatch myCountDownLatch;
@Override
public void handleMessage(Message theMessage) throws MessagingException {
if (myCountDownLatch != null) {
myCountDownLatch.countDown();
}
if (myPauseEveryMessage) {
waitIfPaused();
}
super.handleMessage(theMessage);
}
private synchronized void waitIfPaused() {
try {
if (myPauseEveryMessage) {
wait();
}
} catch (InterruptedException theE) {
ourLog.error("interrupted", theE);
}
}
public void pause() {
myPauseEveryMessage = true;
}
public synchronized void unPause() {
myPauseEveryMessage = false;
notifyAll();
}
public void setCountDownLatch(CountDownLatch theCountDownLatch) {
myCountDownLatch = theCountDownLatch;
}
}

View File

@ -1,28 +1,23 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
import ca.uhn.fhir.jpa.subscription.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.*; import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*; import static org.junit.Assert.fail;
@Configuration @Configuration
@Import(TestJPAConfig.class) @Import(TestJPAConfig.class)

View File

@ -2,12 +2,12 @@ package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import org.springframework.beans.BeansException; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import org.springframework.beans.factory.annotation.Autowired; import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
@ -38,4 +38,16 @@ public class TestJPAConfig {
public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) { public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) {
return new UnregisterScheduledProcessor(theEnv); return new UnregisterScheduledProcessor(theEnv);
} }
@Lazy
@Bean
public SubscriptionTestUtil subscriptionTestUtil() {
return new SubscriptionTestUtil();
}
@Bean
@Primary
public SubscriptionDeliveringRestHookSubscriber stoppableSubscriptionDeliveringRestHookSubscriber() {
return new StoppableSubscriptionDeliveringRestHookSubscriber();
}
} }

View File

@ -1,27 +1,21 @@
package ca.uhn.fhir.jpa.config; package ca.uhn.fhir.jpa.config;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder; import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -102,8 +96,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
DataSource dataSource = ProxyDataSourceBuilder DataSource dataSource = ProxyDataSourceBuilder
.create(retVal) .create(retVal)
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL") // .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS) // .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
// .countQuery(new ThreadQueryCountHolder()) // .countQuery(new ThreadQueryCountHolder())
.countQuery(singleQueryCountHolder()) .countQuery(singleQueryCountHolder())
.build(); .build();

View File

@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
@ -181,6 +182,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao; protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao;
@Autowired @Autowired
private ISearchParamRegistry mySearchParamRegistry; private ISearchParamRegistry mySearchParamRegistry;
@Autowired
protected SubscriptionLoader mySubscriptionLoader;
@Before @Before
public void beforeCreateInterceptor() { public void beforeCreateInterceptor() {

View File

@ -2,8 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription;
@ -23,19 +22,20 @@ import static org.junit.Assert.*;
public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Test { public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Test {
@Autowired @Autowired
private SubscriptionRestHookInterceptor myInterceptor; private DaoConfig myDaoConfig;
@After @After
public void afterResetDao() { public void afterResetDao() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
} }
@Before @Before
public void before() { public void before() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
} }
@Test @Test
@ -83,8 +83,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
}); });
myEntityManager.clear(); myEntityManager.clear();
myInterceptor.start();
} }
/** /**
@ -104,9 +102,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
assertNotNull(id.getIdPart()); assertNotNull(id.getIdPart());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
/** /**
@ -124,9 +119,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless(); IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless();
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
/** /**
@ -145,9 +137,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
assertNotNull(id.getIdPart()); assertNotNull(id.getIdPart());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
@AfterClass @AfterClass

View File

@ -15,7 +15,7 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc; import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl; import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceCountCache;
@ -223,8 +223,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
@Qualifier("mySearchParameterDaoR4") @Qualifier("mySearchParameterDaoR4")
protected IFhirResourceDao<SearchParameter> mySearchParameterDao; protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
@Autowired @Autowired
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
@Autowired
protected ISearchParamRegistry mySearchParamRegsitry; protected ISearchParamRegistry mySearchParamRegsitry;
@Autowired @Autowired
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc; protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
@ -273,6 +271,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
protected ICacheWarmingSvc myCacheWarmingSvc; protected ICacheWarmingSvc myCacheWarmingSvc;
@Autowired @Autowired
private JpaValidationSupportChainR4 myJpaValidationSupportChainR4; private JpaValidationSupportChainR4 myJpaValidationSupportChainR4;
@Autowired
protected SubscriptionRegistry mySubscriptionRegistry;
@After() @After()
public void afterCleanupDao() { public void afterCleanupDao() {

View File

@ -1,23 +1,26 @@
package ca.uhn.fhir.jpa.dao.r4; package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.*; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.ReferenceParam;
import ca.uhn.fhir.rest.server.exceptions.*; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Observation.ObservationStatus;
import org.junit.*; import org.hl7.fhir.r4.model.Task;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Test;
import java.util.*; import java.util.List;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Matchers.eq;
@SuppressWarnings({ "unchecked", "deprecation" }) @SuppressWarnings({"unchecked", "deprecation"})
public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test { public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCreatePlaceholdersR4Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoCreatePlaceholdersR4Test.class);
@ -25,6 +28,7 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
@After @After
public final void afterResetDao() { public final void afterResetDao() {
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets()); myDaoConfig.setAutoCreatePlaceholderReferenceTargets(new DaoConfig().isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setResourceClientIdStrategy(new DaoConfig().getResourceClientIdStrategy());
} }
@Test @Test
@ -97,7 +101,7 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
} }
@Test @Test
public void testUpdateWithBadReferenceIsPermitted() { public void testUpdateWithBadReferenceIsPermittedAlphanumeric() {
assertFalse(myDaoConfig.isAutoCreatePlaceholderReferenceTargets()); assertFalse(myDaoConfig.isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true); myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
@ -105,11 +109,49 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test {
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o, mySrd).getId(); IIdType id = myObservationDao.create(o, mySrd).getId();
try {
myPatientDao.read(new IdType("Patient/FOO"));
fail();
} catch (ResourceNotFoundException e) {
// good
}
o = new Observation(); o = new Observation();
o.setId(id); o.setId(id);
o.setStatus(ObservationStatus.FINAL); o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/FOO"); o.getSubject().setReference("Patient/FOO");
myObservationDao.update(o, mySrd); myObservationDao.update(o, mySrd);
myPatientDao.read(new IdType("Patient/FOO"));
}
@Test
public void testUpdateWithBadReferenceIsPermittedNumeric() {
assertFalse(myDaoConfig.isAutoCreatePlaceholderReferenceTargets());
myDaoConfig.setAutoCreatePlaceholderReferenceTargets(true);
myDaoConfig.setResourceClientIdStrategy(DaoConfig.ClientIdStrategyEnum.ANY);
Observation o = new Observation();
o.setStatus(ObservationStatus.FINAL);
IIdType id = myObservationDao.create(o, mySrd).getId();
try {
myPatientDao.read(new IdType("Patient/999999999999999"));
fail();
} catch (ResourceNotFoundException e) {
// good
}
o = new Observation();
o.setId(id);
o.setStatus(ObservationStatus.FINAL);
o.getSubject().setReference("Patient/999999999999999");
myObservationDao.update(o, mySrd);
myPatientDao.read(new IdType("Patient/999999999999999"));
} }
@AfterClass @AfterClass

View File

@ -2,8 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -12,7 +11,6 @@ import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
@ -23,19 +21,16 @@ import static org.junit.Assert.*;
public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
@Autowired
private SubscriptionRestHookInterceptor myInterceptor;
@After @After
public void afterResetDao() { public void afterResetDao() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
} }
@Before @Before
public void before() { public void before() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
} }
@Test @Test
@ -83,8 +78,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
}); });
myEntityManager.clear(); myEntityManager.clear();
myInterceptor.start();
} }
/** /**
@ -104,9 +97,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
assertNotNull(id.getIdPart()); assertNotNull(id.getIdPart());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
/** /**
@ -124,9 +114,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless(); IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless();
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
/** /**
@ -145,9 +132,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
assertNotNull(id.getIdPart()); assertNotNull(id.getIdPart());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
myInterceptor.start();
} }
@AfterClass @AfterClass

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.model.entity.*;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef; import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
@ -89,6 +90,11 @@ public class SearchParamExtractorR4Test {
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
return null; return null;
} }
@Override
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
// nothing
}
}; };
} }

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test; import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
@ -46,7 +45,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
protected static Server ourServer; protected static Server ourServer;
protected static String ourServerBase; protected static String ourServerBase;
protected static GenericWebApplicationContext ourWebApplicationContext; protected static GenericWebApplicationContext ourWebApplicationContext;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static DatabaseBackedPagingProvider ourPagingProvider; protected static DatabaseBackedPagingProvider ourPagingProvider;
protected static PlatformTransactionManager ourTxManager; protected static PlatformTransactionManager ourTxManager;
protected static Integer ourConnectionPoolSize; protected static Integer ourConnectionPoolSize;
@ -101,7 +99,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
ourWebApplicationContext.setParent(myAppCtx); ourWebApplicationContext.setParent(myAppCtx);
ourWebApplicationContext.refresh(); ourWebApplicationContext.refresh();
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
ourTxManager = ourWebApplicationContext.getBean(PlatformTransactionManager.class); ourTxManager = ourWebApplicationContext.getBean(PlatformTransactionManager.class);
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext); proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);

View File

@ -7,8 +7,6 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.parser.StrictErrorHandler;
@ -60,8 +58,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
protected static GenericWebApplicationContext ourWebApplicationContext; protected static GenericWebApplicationContext ourWebApplicationContext;
protected static SearchParamRegistryDstu3 ourSearchParamRegistry; protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
protected static DatabaseBackedPagingProvider ourPagingProvider; protected static DatabaseBackedPagingProvider ourPagingProvider;
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor;
protected static ISearchDao mySearchEntityDao; protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
private static Server ourServer; private static Server ourServer;
@ -158,8 +154,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class); myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
mySearchEntityDao = wac.getBean(ISearchDao.class); mySearchEntityDao = wac.getBean(ISearchDao.class);
ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
ourEmailSubscriptionInterceptor = wac.getBean(SubscriptionEmailInterceptor.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class); ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class);

View File

@ -3,10 +3,11 @@ package ca.uhn.fhir.jpa.provider.r4;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig; import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test; import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4; import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
@ -33,6 +34,7 @@ import org.hl7.fhir.r4.model.Patient;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.ContextLoader; import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
@ -61,7 +63,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected static ISearchDao mySearchEntityDao; protected static ISearchDao mySearchEntityDao;
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc; protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
protected static GenericWebApplicationContext ourWebApplicationContext; protected static GenericWebApplicationContext ourWebApplicationContext;
protected static SubscriptionRestHookInterceptor ourReskHookSubscriptionInterceptor; protected static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
private static Server ourServer; private static Server ourServer;
protected IGenericClient ourClient; protected IGenericClient ourClient;
protected ResourceCountCache ourResourceCountsCache; protected ResourceCountCache ourResourceCountsCache;
@ -69,6 +71,9 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
private Object ourGraphQLProvider; private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested; private boolean ourRestHookSubscriptionInterceptorRequested;
@Autowired
protected SubscriptionLoader mySubscriptionLoader;
public BaseResourceProviderR4Test() { public BaseResourceProviderR4Test() {
super(); super();
} }
@ -156,7 +161,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class); mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
mySearchEntityDao = wac.getBean(ISearchDao.class); mySearchEntityDao = wac.getBean(ISearchDao.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class); ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
ourReskHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class); ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000); myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
@ -177,19 +182,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
} }
} }
/**
* This is lazy created so we only ask for it if its needed
*/
protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() {
SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
ourRestHookSubscriptionInterceptorRequested = true;
return retVal;
}
protected boolean hasRestHookSubscriptionInterceptor() {
return ourRestHookSubscriptionInterceptorRequested;
}
protected boolean shouldLogClient() { protected boolean shouldLogClient() {
return true; return true;
} }
@ -206,21 +198,20 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
return names; return names;
} }
protected void waitForRegisteredSubscriptionCount(int theSize) throws Exception { protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
for (int i = 0; ; i++) { for (int i = 0; ; i++) {
if (i == 10) { if (i == 10) {
fail("Failed to init subscriptions"); fail("Failed to init subscriptions");
} }
try { try {
getRestHookSubscriptionInterceptor().doInitSubscriptions(); mySubscriptionLoader.initSubscriptions();
break; break;
} catch (ResourceVersionConflictException e) { } catch (ResourceVersionConflictException e) {
Thread.sleep(250); Thread.sleep(250);
} }
} }
SubscriptionRestHookInterceptor interceptor = getRestHookSubscriptionInterceptor(); TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
TestUtil.waitForSize(theSize, () -> interceptor.getRegisteredSubscriptions().size());
Thread.sleep(500); Thread.sleep(500);
} }

View File

@ -10,6 +10,8 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.CloseableHttpResponse;
@ -19,13 +21,10 @@ import org.apache.http.entity.StringEntity;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.hl7.fhir.r4.model.Bundle.HTTPVerb; import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Test; import org.junit.Test;
@ -236,6 +235,32 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
} }
@Test @Test
public void testCreateReflexResourceTheHardWay() throws IOException, ServletException {
ServerOperationInterceptorAdapter interceptor = new ReflexInterceptor();
ourRestServer.registerInterceptor(interceptor);
try {
Patient p = new Patient();
p.setActive(true);
IIdType pid = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
Bundle observations = ourClient
.search()
.forResource("Observation")
.where(Observation.SUBJECT.hasId(pid))
.returnBundle(Bundle.class)
.execute();
assertEquals(1, observations.getEntry().size());
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(observations));
} finally {
ourRestServer.unregisterInterceptor(interceptor);
}
}
@Test
public void testCreateResourceWithVersionedReference() throws IOException, ServletException { public void testCreateResourceWithVersionedReference() throws IOException, ServletException {
String methodName = "testCreateResourceWithVersionedReference"; String methodName = "testCreateResourceWithVersionedReference";
@ -353,6 +378,26 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
} }
} }
public class ReflexInterceptor extends ServerOperationInterceptorAdapter {
@Override
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
if (theResource instanceof Patient) {
((ServletRequestDetails) theRequest).getServletRequest().setAttribute("CREATED_PATIENT", theResource);
}
}
@Override
public void processingCompletedNormally(ServletRequestDetails theRequestDetails) {
Patient createdPatient = (Patient) theRequestDetails.getServletRequest().getAttribute("CREATED_PATIENT");
if (createdPatient != null) {
Observation observation = new Observation();
observation.setSubject(new Reference(createdPatient.getId()));
ourClient.create().resource(observation).execute();
}
}
}
@AfterClass @AfterClass
public static void afterClassClearContext() { public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest(); TestUtil.clearAllStaticFieldsForUnitTest();

View File

@ -2772,6 +2772,105 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>")); assertThat(actual.getText().getDiv().getValueAsString(), containsString("<td>Identifier</td><td>testSearchByResourceChain01</td>"));
} }
@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 @Test
public void testSearchBundleDoesntIncludeTextElement() throws Exception { public void testSearchBundleDoesntIncludeTextElement() throws Exception {
HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json"); HttpGet read = new HttpGet(ourServerBase + "/Patient?_format=json");

View File

@ -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.dao.data.IResourceTableDao;
import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity; import ca.uhn.fhir.jpa.entity.ResourceReindexJobEntity;
import ca.uhn.fhir.jpa.model.entity.ResourceTable; 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.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -63,6 +64,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
@Captor @Captor
private ArgumentCaptor<Date> myHighCaptor; private ArgumentCaptor<Date> myHighCaptor;
private ResourceReindexJobEntity mySingleJob; private ResourceReindexJobEntity mySingleJob;
@Mock
private ISearchParamRegistry mySearchParamRegistry;
@Override @Override
protected FhirContext getContext() { protected FhirContext getContext() {
@ -87,6 +90,7 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
mySvc.setReindexJobDaoForUnitTest(myReindexJobDao); mySvc.setReindexJobDaoForUnitTest(myReindexJobDao);
mySvc.setResourceTableDaoForUnitTest(myResourceTableDao); mySvc.setResourceTableDaoForUnitTest(myResourceTableDao);
mySvc.setTxManagerForUnitTest(myTxManager); mySvc.setTxManagerForUnitTest(myTxManager);
mySvc.setSearchParamRegistryForUnitTest(mySearchParamRegistry);
mySvc.start(); mySvc.start();
} }
@ -175,6 +179,8 @@ public class ResourceReindexingSvcImplTest extends BaseJpaTest {
verify(myReindexJobDao, times(1)).getReindexCount(any()); verify(myReindexJobDao, times(1)).getReindexCount(any());
verify(myReindexJobDao, times(1)).setReindexCount(any(), anyInt()); verify(myReindexJobDao, times(1)).setReindexCount(any(), anyInt());
verifyNoMoreInteractions(myReindexJobDao); verifyNoMoreInteractions(myReindexJobDao);
verify(mySearchParamRegistry, times(1)).forceRefresh();
} }
@Test @Test

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
@ -26,7 +24,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.SubscribableChannel;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -39,7 +37,6 @@ import java.util.List;
public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test { public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class);
private static int ourListenerPort; private static int ourListenerPort;
private static RestfulServer ourListenerRestServer; private static RestfulServer ourListenerRestServer;
private static Server ourListenerServer; private static Server ourListenerServer;
@ -50,9 +47,9 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@Autowired @Autowired
private SingleQueryCountHolder myCountHolder; private SingleQueryCountHolder myCountHolder;
@Autowired @Autowired
protected DaoConfig myDaoConfig; protected SubscriptionTestUtil mySubscriptionTestUtil;
@Autowired @Autowired
private DaoRegistry myDaoRegistry; protected SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
protected CountingInterceptor myCountingInterceptor; protected CountingInterceptor myCountingInterceptor;
@ -65,7 +62,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false); SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false);
for (IIdType next : mySubscriptionIds) { for (IIdType next : mySubscriptionIds) {
IIdType nextId = next.toUnqualifiedVersionless(); IIdType nextId = next.toUnqualifiedVersionless();
@ -81,12 +78,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
@ -101,12 +98,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) { for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
ourClient.delete().resource(next).execute(); ourClient.delete().resource(next).execute();
} }
waitForRegisteredSubscriptionCount(0); waitForActivatedSubscriptionCount(0);
ExecutorSubscribableChannel processingChannel = (ExecutorSubscribableChannel) getRestHookSubscriptionInterceptor().getProcessingChannel(); SubscriptionChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
processingChannel.setInterceptors(new ArrayList<>()); processingChannel.clearInterceptorsForUnitTest();
myCountingInterceptor = new CountingInterceptor(); myCountingInterceptor = new CountingInterceptor();
processingChannel.addInterceptor(myCountingInterceptor); processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
} }
@ -135,7 +132,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
protected void waitForQueueToDrain() throws InterruptedException { protected void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.waitForQueueToDrain();
} }
@PostConstruct @PostConstruct
@ -156,8 +153,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute(); MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
String observationId = methodOutcome.getId().getIdPart(); observation.setId(methodOutcome.getId());
observation.setId(observationId);
return observation; return observation;
} }

View File

@ -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.Message;
import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageChannel;

View File

@ -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.ISearchParamProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; 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 ca.uhn.fhir.rest.api.MethodOutcome;
import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.Enumerations;
@ -20,18 +18,18 @@ import static org.junit.Assert.assertEquals;
public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test { public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
@Autowired @Autowired
BaseSearchParamRegistry mySearchParamRegistry; ISearchParamRegistry mySearchParamRegistry;
@Autowired @Autowired
ISearchParamProvider origSearchParamProvider; ISearchParamProvider origSearchParamProvider;
@Before @Before
public void useFhirClientSearchParamProvider() { public void useFhirClientSearchParamProvider() {
mySearchParamRegistry.setSearchParamProvider(new FhirClientSearchParamProvider(ourClient)); mySearchParamRegistry.setSearchParamProviderForUnitTest(new FhirClientSearchParamProvider(ourClient));
} }
@After @After
public void revert() { public void revert() {
mySearchParamRegistry.setSearchParamProvider(origSearchParamProvider); mySearchParamRegistry.setSearchParamProviderForUnitTest(origSearchParamProvider);
} }
@Test @Test
@ -48,7 +46,7 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
mySearchParameterDao.create(sp); mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh(); mySearchParamRegsitry.forceRefresh();
createSubscription(criteria, "application/json"); createSubscription(criteria, "application/json");
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
{ {
Observation observation = new Observation(); Observation observation = new Observation();
@ -81,8 +79,5 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
waitForQueueToDrain(); waitForQueueToDrain();
waitForSize(2, ourUpdatedObservations); waitForSize(2, ourUpdatedObservations);
} }
} }
} }

View File

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

View File

@ -1,10 +1,11 @@
package ca.uhn.fhir.jpa.subscription.r4; package ca.uhn.fhir.jpa.subscription;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; 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 { public class FhirR4Util {

View File

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

View File

@ -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.provider.BaseResourceProviderDstu2Test;
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt; import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
@ -22,8 +19,6 @@ import org.junit.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
@ -42,12 +37,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
private List<IIdType> mySubscriptionIds = new ArrayList<>(); private List<IIdType> mySubscriptionIds = new ArrayList<>();
@Autowired @Autowired
private SubscriptionEmailInterceptor mySubscriber; private SubscriptionTestUtil mySubscriptionTestUtil;
@Autowired
private List<IFhirResourceDao<?>> myResourceDaos;
@Autowired
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
private AsyncTaskExecutor myAsyncTaskExecutor;
@After @After
public void after() throws Exception { public void after() throws Exception {
@ -58,38 +48,16 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
ourClient.delete().resourceById(next).execute(); ourClient.delete().resourceById(next).execute();
} }
mySubscriptionIds.clear(); mySubscriptionIds.clear();
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
ourRestServer.unregisterInterceptor(mySubscriber);
} }
@Before @Before
public void before() throws Exception { public void before() throws Exception {
super.before(); super.before();
JavaMailEmailSender emailSender = new JavaMailEmailSender(); mySubscriptionTestUtil.initEmailSender(ourListenerPort);
emailSender.setSmtpServerHostname("localhost");
emailSender.setSmtpServerPort(ourListenerPort);
emailSender.start();
mySubscriber.setEmailSender(emailSender); mySubscriptionTestUtil.registerEmailInterceptor();
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();
} }
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { 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()); subscription.setId(methodOutcome.getId().getIdPart());
mySubscriptionIds.add(methodOutcome.getId()); mySubscriptionIds.add(methodOutcome.getId());
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
return subscription; return subscription;
} }
@ -138,8 +106,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload, "to1@example.com,to2@example.com"); 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()); assertEquals(0, Arrays.asList(ourTestSmtp.getReceivedMessages()).size());
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");

View File

@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.subscription.email;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.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.testutil.RandomServerPortProvider;
import ca.uhn.fhir.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.icegreen.greenmail.store.FolderException; 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.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
@ -21,7 +22,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
/** /**
* Test the rest-hook subscriptions * Test the rest-hook subscriptions
@ -29,6 +30,10 @@ import static org.junit.Assert.*;
public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmailSubscriptionDstu3Test.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmailSubscriptionDstu3Test.class);
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
private static List<Observation> ourCreatedObservations = Lists.newArrayList(); private static List<Observation> ourCreatedObservations = Lists.newArrayList();
private static int ourListenerPort; private static int ourListenerPort;
private static List<String> ourContentTypes = new ArrayList<>(); private static List<String> ourContentTypes = new ArrayList<>();
@ -51,23 +56,17 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(ourEmailSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterEmailListener() throws FolderException { public void beforeRegisterEmailListener() throws FolderException {
ourTestSmtp.purgeEmailFromAllMailboxes(); ourTestSmtp.purgeEmailFromAllMailboxes();
; mySubscriptionTestUtil.registerEmailInterceptor();
ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor);
JavaMailEmailSender emailSender = new JavaMailEmailSender(); mySubscriptionTestUtil.initEmailSender(ourListenerPort);
emailSender.setSmtpServerHostname("localhost");
emailSender.setSmtpServerPort(ourListenerPort);
emailSender.start();
ourEmailSubscriptionInterceptor.setEmailSender(emailSender); myDaoConfig.setEmailFromAddress("123@hapifhir.io");
ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io");
} }
private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException { private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException {
@ -115,8 +114,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
String code = "1000000050"; String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
createSubscription(criteria1, payload); Subscription subscription = createSubscription(criteria1, payload);
waitForQueueToDrain(); waitForQueueToDrain();
mySubscriptionTestUtil.setEmailSender(subscription.getIdElement());
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
waitForQueueToDrain(); waitForQueueToDrain();
@ -150,19 +150,18 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Assert.assertNotNull(subscriptionTemp); Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension() subscriptionTemp.getChannel().addExtension()
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("mailto:myfrom@from.com")); .setValue(new StringType("mailto:myfrom@from.com"));
subscriptionTemp.getChannel().addExtension() subscriptionTemp.getChannel().addExtension()
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject")); .setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp)); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp));
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain(); waitForQueueToDrain();
mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
waitForQueueToDrain(); waitForQueueToDrain();
@ -197,10 +196,10 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId()); Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
Assert.assertNotNull(subscriptionTemp); Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension() subscriptionTemp.getChannel().addExtension()
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("myfrom@from.com")); .setValue(new StringType("myfrom@from.com"));
subscriptionTemp.getChannel().addExtension() subscriptionTemp.getChannel().addExtension()
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE) .setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
.setValue(new StringType("This is a subject")); .setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
@ -208,6 +207,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Subscription ID is: {}", id.getValue()); ourLog.info("Subscription ID is: {}", id.getValue());
waitForQueueToDrain(); waitForQueueToDrain();
mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
waitForQueueToDrain(); waitForQueueToDrain();
@ -238,7 +238,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }
@AfterClass @AfterClass

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.subscription.email; 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 ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.GreenMailUtil; import com.icegreen.greenmail.util.GreenMailUtil;
@ -15,7 +17,7 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import java.util.Arrays; import java.util.Arrays;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
public class JavaMailEmailSenderTest { public class JavaMailEmailSenderTest {

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 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.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.param.*; import ca.uhn.fhir.rest.param.*;
@ -29,18 +29,18 @@ import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestR4Config.class}) @ContextConfiguration(classes = {TestR4Config.class})
public class SubscriptionMatcherInMemoryTestR4 { public class InMemorySubscriptionMatcherTestR4 {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionMatcherInMemoryTestR4.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InMemorySubscriptionMatcherTestR4.class);
@Autowired @Autowired
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory; InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
@Autowired @Autowired
FhirContext myContext; FhirContext myContext;
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) { private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
String criteria = params.toNormalizedQueryString(myContext); String criteria = params.toNormalizedQueryString(myContext);
ourLog.info("Criteria: <{}>", criteria); ourLog.info("Criteria: <{}>", criteria);
return mySubscriptionMatcherInMemory.match(criteria, resource); return myInMemorySubscriptionMatcher.match(criteria, resource);
} }
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) { private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
@ -380,18 +380,16 @@ public class SubscriptionMatcherInMemoryTestR4 {
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam")); params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam"));
try { try {
String criteria = params.toNormalizedQueryString(myContext); String criteria = params.toNormalizedQueryString(myContext);
ResourceModifiedMessage msg = new ResourceModifiedMessage(); ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE);
msg.setSubscriptionId("Subscription/123"); msg.setSubscriptionId("Subscription/123");
msg.setId(new IdType("Patient/ABC")); msg.setId(new IdType("Patient/ABC"));
msg.setNewPayload(myContext, patient); SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, msg);
SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, msg);
fail(); fail();
} catch (InternalErrorException e){ } 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()); 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 @Test
public void testSearchResourceReferenceOnlyCorrectPath() { public void testSearchResourceReferenceOnlyCorrectPath() {
Organization org = new Organization(); Organization org = new Organization();

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.*;
import org.junit.*; import org.junit.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
@ -28,7 +29,6 @@ import java.util.Enumeration;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test { public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test {
@ -41,20 +41,23 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>()); private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>()); private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void afterResetSubscriptionActivatingInterceptor() { public void afterResetSubscriptionActivatingInterceptor() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
} }
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeSetSubscriptionActivatingInterceptor() { public void beforeSetSubscriptionActivatingInterceptor() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
getRestHookSubscriptionInterceptor().initSubscriptions(); mySubscriptionLoader.initSubscriptions();
} }
@ -105,11 +108,8 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
createSubscription(criteria1, payload, ourListenerServerBase); createSubscription(criteria1, payload, ourListenerServerBase);
createSubscription(criteria2, payload, ourListenerServerBase); createSubscription(criteria2, payload, ourListenerServerBase);
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.registerRestHookInterceptor();
getRestHookSubscriptionInterceptor().initSubscriptions(); mySubscriptionLoader.initSubscriptions();
assertTrue(hasRestHookSubscriptionInterceptor());
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
@ -120,9 +120,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
if (hasRestHookSubscriptionInterceptor()) { mySubscriptionTestUtil.waitForQueueToDrain();
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
}
} }
public static class ObservationListener implements IResourceProvider { public static class ObservationListener implements IResourceProvider {

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; 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.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Observation; 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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; 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 * Test the rest-hook subscriptions
@ -47,6 +50,9 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
private static List<String> ourUpdatedObservations = Lists.newArrayList(); private static List<String> ourUpdatedObservations = Lists.newArrayList();
private List<IIdType> mySubscriptionIds = new ArrayList<IIdType>(); private List<IIdType> mySubscriptionIds = new ArrayList<IIdType>();
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourLog.info("** AFTER **"); ourLog.info("** AFTER **");
@ -62,12 +68,12 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
@ -279,7 +285,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }
@BeforeClass @BeforeClass
@ -309,18 +315,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
ourListenerServer.stop(); 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 { public static class ObservationListener implements IResourceProvider {
@Create @Create

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
@ -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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,6 +47,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>()); private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourLog.info("**** Starting @After *****"); ourLog.info("**** Starting @After *****");
@ -61,13 +66,12 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
@ -346,7 +350,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }
@BeforeClass @BeforeClass

View File

@ -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.config.StoppableSubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
import ca.uhn.fhir.jpa.dao.DaoRegistry; import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
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.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome; 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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; 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.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.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.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.ExecutorSubscribableChannel;
import javax.annotation.PostConstruct; import java.util.concurrent.CountDownLatch;
import javax.servlet.http.HttpServletRequest; import java.util.concurrent.TimeUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
@ -46,7 +30,16 @@ import static org.junit.Assert.*;
* Test the rest-hook subscriptions * Test the rest-hook subscriptions
*/ */
public class RestHookTestR4Test extends BaseSubscriptionsR4Test { 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 @Test
public void testRestHookSubscriptionApplicationFhirJson() throws Exception { public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
@ -58,7 +51,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
createSubscription(criteria1, payload); createSubscription(criteria1, payload);
createSubscription(criteria2, payload); createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
@ -75,10 +68,10 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String payload = "application/fhir+json"; String payload = "application/fhir+json";
createSubscription(criteria, payload); createSubscription(criteria, payload);
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
Integer changes = ourReskHookSubscriptionInterceptor.doInitSubscriptions(); int changes = this.mySubscriptionLoader.doInitSubscriptionsForUnitTest();
assertEquals(0, changes.intValue()); assertEquals(0, changes);
} }
} }
@ -92,7 +85,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
createSubscription(criteria1, payload); createSubscription(criteria1, payload);
createSubscription(criteria2, payload); createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
Observation obs = sendObservation(code, "SNOMED-CT"); Observation obs = sendObservation(code, "SNOMED-CT");
@ -122,32 +115,137 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String code = "1000000050"; String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
waitForRegisteredSubscriptionCount(0); waitForActivatedSubscriptionCount(0);
Subscription subscription1 = createSubscription(criteria1, payload); 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"); ourLog.info("** About to send observation");
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain(); waitForQueueToDrain();
waitForSize(0, ourCreatedObservations); waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations); waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0)); assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertEquals(observation1.getIdElement().getIdPart(), ourUpdatedObservations.get(0).getIdElement().getIdPart()); IdType idElement = ourUpdatedObservations.get(0).getIdElement();
assertEquals(null, ourUpdatedObservations.get(0).getIdElement().getVersionIdPart()); 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 @Test
@ -160,7 +258,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload); Subscription subscription2 = createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");
@ -240,7 +338,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload); Subscription subscription2 = createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");
@ -318,7 +416,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload); Subscription subscription2 = createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
ourLog.info("** About to send obervation"); ourLog.info("** About to send obervation");
Observation observation1 = sendObservation(code, "SNOMED-CT"); Observation observation1 = sendObservation(code, "SNOMED-CT");
@ -384,7 +482,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
@Test @Test
public void testSubscriptionTriggerViaSubscription() throws Exception { public void testSubscriptionTriggerViaSubscription() throws Exception {
BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true); SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true);
String payload = "application/xml"; String payload = "application/xml";
@ -392,7 +490,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
createSubscription(criteria1, payload); createSubscription(criteria1, payload);
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
ourLog.info("** About to send obervation"); ourLog.info("** About to send obervation");
@ -490,7 +588,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
Subscription subscription1 = createSubscription(criteria1, payload); Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload); Subscription subscription2 = createSubscription(criteria2, payload);
waitForRegisteredSubscriptionCount(2); waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT"); 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 // Add some headers, and we'll also turn back to requested status for fun
Subscription subscription = createSubscription(criteria1, payload); Subscription subscription = createSubscription(criteria1, payload);
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
subscription.getChannel().addHeader("X-Foo: FOO"); subscription.getChannel().addHeader("X-Foo: FOO");
subscription.getChannel().addHeader("X-Bar: BAR"); subscription.getChannel().addHeader("X-Bar: BAR");
@ -553,7 +651,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml"; String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscription = createSubscription(criteria1, payload); Subscription subscription = createSubscription(criteria1, payload);
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
@ -644,7 +742,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
mySearchParameterDao.create(sp); mySearchParameterDao.create(sp);
mySearchParamRegsitry.forceRefresh(); mySearchParamRegsitry.forceRefresh();
createSubscription(criteria, "application/json"); createSubscription(criteria, "application/json");
waitForRegisteredSubscriptionCount(1); waitForActivatedSubscriptionCount(1);
{ {
Observation bodySite = new Observation(); Observation bodySite = new Observation();

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test; 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.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Observation; 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.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -42,6 +44,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
private static String ourListenerServerBase; private static String ourListenerServerBase;
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourLog.info("** AFTER **"); ourLog.info("** AFTER **");
@ -51,12 +56,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
@ -64,11 +69,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
ourCreatedObservations.clear(); ourCreatedObservations.clear();
ourUpdatedObservations.clear(); ourUpdatedObservations.clear();
ourRestHookSubscriptionInterceptor.initSubscriptions(); mySubscriptionLoader.initSubscriptions();
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }

View File

@ -1,29 +1,30 @@
package ca.uhn.fhir.jpa.subscription; package ca.uhn.fhir.jpa.subscription.resthook;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.List;
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.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.*; import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import com.google.common.collect.Lists; import java.util.Collections;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.*;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
/** /**
* Test the rest-hook subscriptions * 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 final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.class);
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@Override @Override
protected boolean shouldLogClient() { protected boolean shouldLogClient() {
return false; return false;
@ -51,20 +55,20 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
public void beforeReset() { public void beforeReset() {
ourCreatedObservations.clear(); ourCreatedObservations.clear();
ourUpdatedObservations.clear(); ourUpdatedObservations.clear();
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
} }
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException { 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 { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }
private Observation sendObservation(String code, String system) throws InterruptedException { private Observation sendObservation(String code, String system) throws InterruptedException {

View File

@ -1,28 +1,30 @@
package ca.uhn.fhir.jpa.subscription.r4; package ca.uhn.fhir.jpa.subscription.resthook;
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;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; 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.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt; 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.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer; 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 * 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 final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class);
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList()); private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@Override @Override
protected boolean shouldLogClient() { protected boolean shouldLogClient() {
return false; return false;
@ -50,12 +55,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
myDaoConfig.getInterceptors().remove(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
myDaoConfig.getInterceptors().add(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before
@ -84,11 +89,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests()); ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests());
while (getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests() > 0) { while (mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests() > 0) {
Thread.sleep(250); 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 { private Observation sendObservation(String code, String system) throws InterruptedException {

View File

@ -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.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; 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 ca.uhn.fhir.rest.api.MethodOutcome;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
@ -11,6 +13,7 @@ import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -44,6 +47,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
private String mySubscriptionId; private String mySubscriptionId;
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>()); private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@Override @Override
@After @After
public void after() throws Exception { public void after() throws Exception {
@ -64,7 +70,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
} }
@Override @Override
@ -76,6 +82,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
} }
/**
* Ignored because this feature isn't implemented yet
*/
@Test @Test
@Ignore @Ignore
public void testSubscriptionAddedTrigger() { public void testSubscriptionAddedTrigger() {
@ -122,7 +131,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor()); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
@Before @Before

View File

@ -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.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; 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.jpa.util.JpaConstants;
import ca.uhn.fhir.rest.annotation.Create; import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.ResourceParam;
@ -50,6 +52,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
private static List<String> ourContentTypes = new ArrayList<>(); private static List<String> ourContentTypes = new ArrayList<>();
private List<IIdType> mySubscriptionIds = new ArrayList<>(); private List<IIdType> mySubscriptionIds = new ArrayList<>();
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourLog.info("**** Starting @After *****"); ourLog.info("**** Starting @After *****");
@ -66,7 +71,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
ourLog.info("Done deleting all subscriptions"); ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
mySubscriptionTriggeringSvc.cancelAll(); mySubscriptionTriggeringSvc.cancelAll();
mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null); mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null);
@ -79,7 +84,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
@Before @Before
public void beforeRegisterRestHookListener() { public void beforeRegisterRestHookListener() {
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.registerRestHookInterceptor();
} }
/** /**
@ -376,7 +381,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
} }
private void waitForQueueToDrain() throws InterruptedException { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor); mySubscriptionTestUtil.waitForQueueToDrain();
} }
public static class ObservationListener implements IResourceProvider { public static class ObservationListener implements IResourceProvider {

View File

@ -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.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.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
@ -27,7 +29,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 // This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore @Ignore

View File

@ -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.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.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
@ -18,7 +20,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 // This is currently disabled as the criteria mechanism was a non-standard experiment
@Ignore @Ignore

View File

@ -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.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.FhirR4Util;
import ca.uhn.fhir.jpa.subscription.SocketImplementation; import ca.uhn.fhir.jpa.subscription.SocketImplementation;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
@ -19,7 +20,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 // This is currently disabled as the criteria mechanism was a non-standard experiment

View File

@ -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.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.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt; import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
@ -21,13 +23,14 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI; import java.net.URI;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 * 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 WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation; private SocketImplementation mySocketImplementation;
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void after() throws Exception { public void after() throws Exception {
super.after(); super.after();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
ourRestServer.unregisterInterceptor(interceptor);
} }
@After @After
@ -69,8 +74,7 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs
public void before() throws Exception { public void before() throws Exception {
super.before(); super.before();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.registerWebSocketInterceptor();
ourRestServer.registerInterceptor(interceptor);
/* /*
* Create patient * Create patient

View File

@ -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.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.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
@ -12,13 +14,14 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI; import java.net.URI;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 * 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 WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation; private SocketImplementation mySocketImplementation;
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@After @After
public void after() throws Exception { public void after() throws Exception {
super.after(); super.after();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
ourRestServer.unregisterInterceptor(interceptor);
} }
@After @After
@ -67,8 +72,7 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
myDaoConfig.setSubscriptionEnabled(true); myDaoConfig.setSubscriptionEnabled(true);
myDaoConfig.setSubscriptionPollDelay(0L); myDaoConfig.setSubscriptionPollDelay(0L);
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.registerWebSocketInterceptor();
ourRestServer.registerInterceptor(interceptor);
/* /*
* Create patient * Create patient

View File

@ -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.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.FhirR4Util;
import ca.uhn.fhir.jpa.subscription.SocketImplementation; 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.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
@ -13,13 +14,14 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import java.net.URI; import java.net.URI;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.contains; 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 * 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 WebSocketClient myWebSocketClient;
private SocketImplementation mySocketImplementation; private SocketImplementation mySocketImplementation;
@Autowired
private SubscriptionTestUtil mySubscriptionTestUtil;
@Override @Override
@After @After
public void after() throws Exception { public void after() throws Exception {
super.after(); super.after();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
ourRestServer.unregisterInterceptor(interceptor);
} }
@After @After
@ -66,8 +70,7 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes
public void before() throws Exception { public void before() throws Exception {
super.before(); super.before();
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class); mySubscriptionTestUtil.registerWebSocketInterceptor();
ourRestServer.registerInterceptor(interceptor);
/* /*
* Create patient * Create patient

View File

@ -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.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.TestUtil;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; 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<String> expansionCodes = expansion
.getExpansion()
.getContains()
.stream()
.map(t -> t.getCode())
.sorted()
.collect(Collectors.toList());
assertEquals(Lists.newArrayList("A","C"), expansionCodes);
}
public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) { public static List<String> toCodesContains(List<ValueSet.ValueSetExpansionContainsComponent> theContains) {
List<String> retVal = new ArrayList<>(); List<String> retVal = new ArrayList<>();

View File

@ -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. 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. 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. Execute the `build-docker-image.sh` script to build the docker image.

View File

@ -1,23 +1,6 @@
package ca.uhn.fhir.jpa.demo; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; 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.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.*; import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; 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 { public class JpaServerDemo extends RestfulServer {
@ -134,12 +127,10 @@ public class JpaServerDemo extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); 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<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
for (IServerInterceptor interceptor : interceptorBeans) { subscriptionInterceptorLoader.registerInterceptors();
this.registerInterceptor(interceptor);
}
/* /*
* If you are hosting this server at a specific DNS name, the server will try to * If you are hosting this server at a specific DNS name, the server will try to

View File

@ -1,16 +1,6 @@
package ca.uhn.fhir.jpa.demo; 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.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; 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.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.*; import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; 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 { public class JpaServerDemoDstu2 extends RestfulServer {
@ -128,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class)); 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<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values(); SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
for (IServerInterceptor interceptor : interceptorBeans) { subscriptionInterceptorLoader.registerInterceptors();
this.registerInterceptor(interceptor);
}
/* /*
* If you are hosting this server at a specific DNS name, the server will try to * If you are hosting this server at a specific DNS name, the server will try to

View File

@ -22,10 +22,19 @@ package ca.uhn.fhir.jpa.migrate;
import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask; import ca.uhn.fhir.jpa.migrate.taskdef.BaseTableColumnTypeTask;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver; import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver;
import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter;
import org.hibernate.engine.jdbc.dialect.spi.DialectResolver; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.ColumnMapRowMapper; import org.springframework.jdbc.core.ColumnMapRowMapper;
@ -53,7 +62,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); 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<String> indexNames = new HashSet<>(); Set<String> indexNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -81,7 +90,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); 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()) { while (indexes.next()) {
String indexName = indexes.getString("INDEX_NAME"); String indexName = indexes.getString("INDEX_NAME");
@ -112,7 +121,7 @@ public class JdbcUtils {
metadata = connection.getMetaData(); metadata = connection.getMetaData();
String catalog = connection.getCatalog(); String catalog = connection.getCatalog();
String schema = connection.getSchema(); 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()) { while (indexes.next()) {
@ -165,7 +174,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); 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<String> columnNames = new HashSet<>(); Set<String> columnNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -201,7 +210,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); 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<String> columnNames = new HashSet<>(); Set<String> columnNames = new HashSet<>();
while (indexes.next()) { while (indexes.next()) {
@ -233,27 +242,83 @@ public class JdbcUtils {
Set<String> sequenceNames = new HashSet<>(); Set<String> sequenceNames = new HashSet<>();
if (dialect.supportsSequences()) { if (dialect.supportsSequences()) {
String sql = dialect.getQuerySequencesString();
if (sql != null) {
Statement statement = null; // Use Hibernate to get a list of current sequences
ResultSet rs = null; SequenceInformationExtractor sequenceInformationExtractor = dialect.getSequenceInformationExtractor();
try { ExtractionContext extractionContext = new ExtractionContext.EmptyExtractionContext() {
statement = connection.createStatement(); @Override
rs = statement.executeQuery(sql); public Connection getJdbcConnection() {
return connection;
while (rs.next()) {
sequenceNames.add(rs.getString(1).toUpperCase());
}
} finally {
if (rs != null) rs.close();
if (statement != null) statement.close();
} }
@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<SequenceInformation> sequences = sequenceInformationExtractor.extractMetadata(extractionContext);
for (SequenceInformation next : sequences) {
sequenceNames.add(next.getSequenceName().getSequenceName().getText());
} }
} }
return sequenceNames; return sequenceNames;
} catch (SQLException e ) { } catch (SQLException e) {
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
}); });
@ -298,7 +363,7 @@ public class JdbcUtils {
DatabaseMetaData metadata; DatabaseMetaData metadata;
try { try {
metadata = connection.getMetaData(); 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()) { while (tables.next()) {
String tableName = toUpperCase(tables.getString("TABLE_NAME"), Locale.US); 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;
}
} }

View File

@ -66,6 +66,13 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MSSQL_2012, "datetime2"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.MSSQL_2012, "datetime2");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.ORACLE_12C, "timestamp"); setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.ORACLE_12C, "timestamp");
setColumnType(ColumnTypeEnum.DATE_TIMESTAMP, DriverTypeEnum.POSTGRES_9_4, "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() { public ColumnTypeEnum getColumnType() {
@ -157,6 +164,13 @@ public abstract class BaseTableColumnTypeTask<T extends BaseTableTask> extends B
return "timestamp"; return "timestamp";
} }
}, },
BOOLEAN {
@Override
public String getDescriptor(Long theColumnLength) {
Assert.isTrue(theColumnLength == null, "Must not supply a column length");
return "boolean";
}
},
INT { INT {
@Override @Override
public String getDescriptor(Long theColumnLength) { public String getDescriptor(Long theColumnLength) {

View File

@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.model.entity;
* #L% * #L%
*/ */
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.Subscription;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -53,6 +55,8 @@ public class ModelConfig {
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>(); private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS); private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
private boolean myDefaultSearchParamsCanBeOverridden = false; private boolean myDefaultSearchParamsCanBeOverridden = false;
private Set<Subscription.SubscriptionChannelType> 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 * 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; 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<Subscription.SubscriptionChannelType> 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) { private static void validateTreatBaseUrlsAsLocal(String theUrl) {
Validate.notBlank(theUrl, "Base URL must not be null or empty"); Validate.notBlank(theUrl, "Base URL must not be null or empty");
@ -309,5 +353,4 @@ public class ModelConfig {
} }
} }

View File

@ -610,12 +610,9 @@ public class ResourceTable extends BaseHasResource implements Serializable {
if (myHasLinks && myResourceLinks != null) { if (myHasLinks && myResourceLinks != null) {
myResourceLinksField = getResourceLinks() myResourceLinksField = getResourceLinks()
.stream() .stream()
.map(t->{ .map(ResourceLink::getTargetResourcePid)
Long retVal = t.getTargetResourcePid();
return retVal;
})
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(t->t.toString()) .map(Object::toString)
.collect(Collectors.joining(" ")); .collect(Collectors.joining(" "));
} else { } else {
myResourceLinksField = null; myResourceLinksField = null;

View File

@ -82,6 +82,10 @@
<artifactId>hapi-fhir-validation-resources-r4</artifactId> <artifactId>hapi-fhir-validation-resources-r4</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<!-- Spring --> <!-- Spring -->
<dependency> <dependency>

View File

@ -38,9 +38,6 @@ import org.hl7.fhir.r4.model.Reference;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -61,9 +58,6 @@ public class ResourceLinkExtractor {
@Autowired @Autowired
private ISearchParamExtractor mySearchParamExtractor; private ISearchParamExtractor mySearchParamExtractor;
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
protected EntityManager myEntityManager;
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver) { public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver) {
String resourceType = theEntity.getResourceType(); String resourceType = theEntity.getResourceType();

View File

@ -36,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -45,30 +44,20 @@ import java.util.*;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implements ISearchParamRegistry { public abstract class BaseSearchParamRegistry<SP extends IBaseResource> 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 int MAX_MANAGED_PARAM_COUNT = 10000;
private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class); private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams; private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams;
private volatile Map<String, List<JpaRuntimeSearchParam>> myActiveUniqueSearchParams = Collections.emptyMap(); private volatile Map<String, List<JpaRuntimeSearchParam>> myActiveUniqueSearchParams = Collections.emptyMap();
private volatile Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap(); private volatile Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap();
@Autowired
private FhirContext myCtx;
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams; private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
@Autowired
private ModelConfig myModelConfig;
private volatile long myLastRefresh; 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 @Override
public void requestRefresh() { public void requestRefresh() {
@ -128,7 +117,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
return Collections.unmodifiableList(retVal); return Collections.unmodifiableList(retVal);
} }
public Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() { private Map<String, Map<String, RuntimeSearchParam>> getBuiltInSearchParams() {
return myBuiltInSearchParams; return myBuiltInSearchParams;
} }
@ -220,10 +209,10 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
public void postConstruct() { public void postConstruct() {
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>(); Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
Set<String> resourceNames = myCtx.getResourceNames(); Set<String> resourceNames = myFhirContext.getResourceNames();
for (String resourceName : resourceNames) { for (String resourceName : resourceNames) {
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(resourceName); RuntimeResourceDefinition nextResDef = myFhirContext.getResourceDefinition(resourceName);
String nextResourceName = nextResDef.getName(); String nextResourceName = nextResDef.getName();
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>(); HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
resourceNameToSearchParams.put(nextResourceName, nameToParam); resourceNameToSearchParams.put(nextResourceName, nameToParam);
@ -284,7 +273,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
continue; continue;
} }
for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) { for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myFhirContext, nextSp)) {
if (isBlank(nextBaseName)) { if (isBlank(nextBaseName)) {
continue; continue;
} }
@ -349,5 +338,9 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
return getActiveSearchParams(theResourceDef.getName()).values(); return getActiveSearchParams(theResourceDef.getName()).values();
} }
@VisibleForTesting
@Override
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
mySearchParamProvider = theSearchParamProvider;
}
} }

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
import com.google.common.annotations.VisibleForTesting;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -59,4 +60,7 @@ public interface ISearchParamRegistry {
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName); RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef); Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
@VisibleForTesting
void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider);
} }

View File

@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry<SearchParameter> { public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry<SearchParameter> {
public SearchParamRegistryDstu2(ISearchParamProvider theSearchParamProvider) {
super(theSearchParamProvider);
}
@Override @Override
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode(); String name = theNextSp.getCode();

View File

@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry<SearchParameter> { public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry<SearchParameter> {
public SearchParamRegistryDstu3(ISearchParamProvider theSearchParamProvider) {
super(theSearchParamProvider);
}
@Override @Override
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) { protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
String name = theNextSp.getCode(); String name = theNextSp.getCode();

Some files were not shown because too many files have changed in this diff Show More