diff --git a/pom.xml b/pom.xml index e411811..e170f17 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-fhir - 3.7.0 + 3.8.0 ca.uhn.hapi.fhir.demo @@ -37,11 +37,19 @@ com.sun.mail javax.mail + + + javax.activation + activation + + + @@ -62,12 +70,24 @@ ca.uhn.hapi.fhir hapi-fhir-jpaserver-base ${project.version} + + + org.springframework + spring-jcl + + ca.uhn.hapi.fhir hapi-fhir-jpaserver-elasticsearch ${project.version} + + + commons-logging + commons-logging + + @@ -117,6 +137,12 @@ org.apache.commons commons-dbcp2 + + + commons-logging + commons-logging + + + + + .*\.txt$ + .*\.html$ + + + + + diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java index cfd8435..e44faac 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java @@ -1,26 +1,21 @@ package ca.uhn.fhir.jpa.starter; -import java.lang.reflect.InvocationTargetException; -import java.sql.Driver; - +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender; import org.apache.commons.dbcp2.BasicDataSource; import org.hl7.fhir.instance.model.Subscription; -import org.springframework.beans.factory.annotation.Autowire; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.annotation.EnableTransactionManagement; - -import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import org.thymeleaf.util.Validate; +import java.lang.reflect.InvocationTargetException; +import java.sql.Driver; + /** * This is the primary configuration file for the example server */ @@ -28,173 +23,149 @@ import org.thymeleaf.util.Validate; @EnableTransactionManagement() public class FhirServerConfigCommon { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class); + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class); - private Boolean allowContainsSearches = HapiProperties.getAllowContainsSearches(); - private Boolean allowMultipleDelete = HapiProperties.getAllowMultipleDelete(); - private Boolean allowExternalReferences = HapiProperties.getAllowExternalReferences(); - private Boolean expungeEnabled = HapiProperties.getExpungeEnabled(); - private Boolean allowPlaceholderReferences = HapiProperties.getAllowPlaceholderReferences(); - private Boolean subscriptionRestHookEnabled = HapiProperties.getSubscriptionRestHookEnabled(); - private Boolean subscriptionEmailEnabled = HapiProperties.getSubscriptionEmailEnabled(); - private Boolean allowOverrideDefaultSearchParams = HapiProperties.getAllowOverrideDefaultSearchParams(); - private String emailFrom = HapiProperties.getEmailFrom(); - private Boolean emailEnabled = HapiProperties.getEmailEnabled(); - private String emailHost = HapiProperties.getEmailHost(); - private Integer emailPort = HapiProperties.getEmailPort(); - private String emailUsername = HapiProperties.getEmailUsername(); - private String emailPassword = HapiProperties.getEmailPassword(); + private Boolean allowContainsSearches = HapiProperties.getAllowContainsSearches(); + private Boolean allowMultipleDelete = HapiProperties.getAllowMultipleDelete(); + private Boolean allowExternalReferences = HapiProperties.getAllowExternalReferences(); + private Boolean expungeEnabled = HapiProperties.getExpungeEnabled(); + private Boolean allowPlaceholderReferences = HapiProperties.getAllowPlaceholderReferences(); + private Boolean subscriptionRestHookEnabled = HapiProperties.getSubscriptionRestHookEnabled(); + private Boolean subscriptionEmailEnabled = HapiProperties.getSubscriptionEmailEnabled(); + private Boolean allowOverrideDefaultSearchParams = HapiProperties.getAllowOverrideDefaultSearchParams(); + private String emailFrom = HapiProperties.getEmailFrom(); + private Boolean emailEnabled = HapiProperties.getEmailEnabled(); + private String emailHost = HapiProperties.getEmailHost(); + private Integer emailPort = HapiProperties.getEmailPort(); + private String emailUsername = HapiProperties.getEmailUsername(); + private String emailPassword = HapiProperties.getEmailPassword(); + @Autowired + private SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory; - public FhirServerConfigCommon() { - ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); - ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); - ourLog.info("Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); - ourLog.info("Server configured to " + (this.expungeEnabled ? "enable" : "disable") + " expunges"); - ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); - ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); + public FhirServerConfigCommon() { + ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); + ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); + ourLog.info("Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); + ourLog.info("Server configured to " + (this.expungeEnabled ? "enable" : "disable") + " expunges"); + ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); + ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); - if (this.emailEnabled) { - ourLog.info("Server is configured to enable email with host '" + this.emailHost + "' and port " + this.emailPort.toString()); - ourLog.info("Server will use '" + this.emailFrom + "' as the from email address"); + if (this.emailEnabled) { + ourLog.info("Server is configured to enable email with host '" + this.emailHost + "' and port " + this.emailPort.toString()); + ourLog.info("Server will use '" + this.emailFrom + "' as the from email address"); - if (this.emailUsername != null && this.emailUsername.length() > 0) { - ourLog.info("Server is configured to use username '" + this.emailUsername + "' for email"); - } + if (this.emailUsername != null && this.emailUsername.length() > 0) { + ourLog.info("Server is configured to use username '" + this.emailUsername + "' for email"); + } - if (this.emailPassword != null && this.emailPassword.length() > 0) { - ourLog.info("Server is configured to use a password for email"); - } - } + if (this.emailPassword != null && this.emailPassword.length() > 0) { + ourLog.info("Server is configured to use a password for email"); + } + } - if (this.subscriptionRestHookEnabled) { - ourLog.info("REST-hook subscriptions enabled"); - } + if (this.subscriptionRestHookEnabled) { + ourLog.info("REST-hook subscriptions enabled"); + } - if (this.subscriptionEmailEnabled) { - ourLog.info("Email subscriptions enabled"); - } - } + if (this.subscriptionEmailEnabled) { + ourLog.info("Email subscriptions enabled"); + } + } - /** - * Configure FHIR properties around the the JPA server via this bean - */ - @Bean() - public DaoConfig daoConfig() { - DaoConfig retVal = new DaoConfig(); + /** + * Configure FHIR properties around the the JPA server via this bean + */ + @Bean() + public DaoConfig daoConfig() { + DaoConfig retVal = new DaoConfig(); - retVal.setAllowContainsSearches(this.allowContainsSearches); - retVal.setAllowMultipleDelete(this.allowMultipleDelete); - retVal.setAllowExternalReferences(this.allowExternalReferences); - retVal.setExpungeEnabled(this.expungeEnabled); - retVal.setAutoCreatePlaceholderReferenceTargets(this.allowPlaceholderReferences); - retVal.setEmailFromAddress(this.emailFrom); + retVal.setAllowContainsSearches(this.allowContainsSearches); + retVal.setAllowMultipleDelete(this.allowMultipleDelete); + retVal.setAllowExternalReferences(this.allowExternalReferences); + retVal.setExpungeEnabled(this.expungeEnabled); + retVal.setAutoCreatePlaceholderReferenceTargets(this.allowPlaceholderReferences); + retVal.setEmailFromAddress(this.emailFrom); - Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); - retVal.setFetchSizeDefaultMaximum(maxFetchSize); - ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE? "'unlimited'": maxFetchSize)); + Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); + retVal.setFetchSizeDefaultMaximum(maxFetchSize); + ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); - Long reuseCachedSearchResultsMillis = HapiProperties.getReuseCachedSearchResultsMillis(); - retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis ); - ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis); - - // Subscriptions are enabled by channel type - if (HapiProperties.getSubscriptionRestHookEnabled()) { - ourLog.info("Enabling REST-hook subscriptions"); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); - } - if (HapiProperties.getSubscriptionEmailEnabled()) { - ourLog.info("Enabling email subscriptions"); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); - } - if (HapiProperties.getSubscriptionWebsocketEnabled()) { - ourLog.info("Enabling websocket subscriptions"); - retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); - } + Long reuseCachedSearchResultsMillis = HapiProperties.getReuseCachedSearchResultsMillis(); + retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); + ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis); - return retVal; - } + // Subscriptions are enabled by channel type + if (HapiProperties.getSubscriptionRestHookEnabled()) { + ourLog.info("Enabling REST-hook subscriptions"); + retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); + } + if (HapiProperties.getSubscriptionEmailEnabled()) { + ourLog.info("Enabling email subscriptions"); + retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); + } + if (HapiProperties.getSubscriptionWebsocketEnabled()) { + ourLog.info("Enabling websocket subscriptions"); + retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET); + } - @Bean - public ModelConfig modelConfig() { - ModelConfig modelConfig = new ModelConfig(); - modelConfig.setAllowContainsSearches(this.allowContainsSearches); - modelConfig.setAllowExternalReferences(this.allowExternalReferences); - modelConfig.setDefaultSearchParamsCanBeOverridden(this.allowOverrideDefaultSearchParams); - modelConfig.setEmailFromAddress(this.emailFrom); + return retVal; + } - // You can enable these if you want to support Subscriptions from your server - if (this.subscriptionRestHookEnabled) { - modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); - } + @Bean + public ModelConfig modelConfig() { + ModelConfig modelConfig = new ModelConfig(); + modelConfig.setAllowContainsSearches(this.allowContainsSearches); + modelConfig.setAllowExternalReferences(this.allowExternalReferences); + modelConfig.setDefaultSearchParamsCanBeOverridden(this.allowOverrideDefaultSearchParams); + modelConfig.setEmailFromAddress(this.emailFrom); - if (this.subscriptionEmailEnabled) { - modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); - } + // You can enable these if you want to support Subscriptions from your server + if (this.subscriptionRestHookEnabled) { + modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); + } - return modelConfig; - } + if (this.subscriptionEmailEnabled) { + modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); + } - /** - * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a - * directory called "jpaserver_derby_files". - * - * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. - */ - @Bean(destroyMethod = "close") - public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { - BasicDataSource retVal = new BasicDataSource(); - Driver driver = (Driver) Class.forName(HapiProperties.getDataSourceDriver()).getConstructor().newInstance(); - retVal.setDriver(driver); - retVal.setUrl(HapiProperties.getDataSourceUrl()); - retVal.setUsername(HapiProperties.getDataSourceUsername()); - retVal.setPassword(HapiProperties.getDataSourcePassword()); - retVal.setMaxTotal(HapiProperties.getDataSourceMaxPoolSize()); - return retVal; - } + return modelConfig; + } + + /** + * The following bean configures the database connection. The 'url' property value of "jdbc:derby:directory:jpaserver_derby_files;create=true" indicates that the server should save resources in a + * directory called "jpaserver_derby_files". + *

+ * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. + */ + @Bean(destroyMethod = "close") + public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + BasicDataSource retVal = new BasicDataSource(); + Driver driver = (Driver) Class.forName(HapiProperties.getDataSourceDriver()).getConstructor().newInstance(); + retVal.setDriver(driver); + retVal.setUrl(HapiProperties.getDataSourceUrl()); + retVal.setUsername(HapiProperties.getDataSourceUsername()); + retVal.setPassword(HapiProperties.getDataSourcePassword()); + retVal.setMaxTotal(HapiProperties.getDataSourceMaxPoolSize()); + return retVal; + } + + @Bean() + public IEmailSender emailSender() { + if (this.emailEnabled) { + JavaMailEmailSender retVal = new JavaMailEmailSender(); + + retVal.setSmtpServerHostname(this.emailHost); + retVal.setSmtpServerPort(this.emailPort); + retVal.setSmtpServerUsername(this.emailUsername); + retVal.setSmtpServerPassword(this.emailPassword); + + Validate.notNull(mySubscriptionDeliveryHandlerFactory, "No subscription delivery handler"); + mySubscriptionDeliveryHandlerFactory.setEmailSender(retVal); - /** - * Do some fancy logging to create a nice access log that has details about each incoming request. - */ - public IServerInterceptor loggingInterceptor() { - LoggingInterceptor retVal = new LoggingInterceptor(); - retVal.setLoggerName(HapiProperties.getLoggerName()); - retVal.setMessageFormat(HapiProperties.getLoggerFormat()); - retVal.setErrorMessageFormat(HapiProperties.getLoggerErrorFormat()); - retVal.setLogExceptions(HapiProperties.getLoggerLogExceptions()); - return retVal; - } + return retVal; + } - /** - * This interceptor adds some pretty syntax highlighting in responses when a browser is detected - */ - @Bean(autowire = Autowire.BY_TYPE) - public IServerInterceptor responseHighlighterInterceptor() { - ResponseHighlighterInterceptor retVal = new ResponseHighlighterInterceptor(); - return retVal; - } - - - @Autowired - private SubscriptionDeliveryHandlerFactory mySubscriptionDeliveryHandlerFactory; - - @Bean() - public IEmailSender emailSender() { - if (this.emailEnabled) { - JavaMailEmailSender retVal = new JavaMailEmailSender(); - - retVal.setSmtpServerHostname(this.emailHost); - retVal.setSmtpServerPort(this.emailPort); - retVal.setSmtpServerUsername(this.emailUsername); - retVal.setSmtpServerPassword(this.emailPassword); - - Validate.notNull(mySubscriptionDeliveryHandlerFactory, "No subscription delivery handler"); - mySubscriptionDeliveryHandlerFactory.setEmailSender(retVal); - - - return retVal; - } - - return null; - } + return null; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java index c0a3403..9e9bcba 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.interceptor.api.IInterceptorService; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.IFhirSystemDao; -import ca.uhn.fhir.jpa.model.interceptor.executor.InterceptorService; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; @@ -17,21 +17,21 @@ import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader; import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor; +import ca.uhn.fhir.jpa.util.ResourceProviderFactory; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; -import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import org.hl7.fhir.dstu3.model.Bundle; import org.hl7.fhir.dstu3.model.Meta; -import org.springframework.web.cors.CorsConfiguration; import org.springframework.context.ApplicationContext; +import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; import java.util.Arrays; -import java.util.List; public class JpaRestfulServer extends RestfulServer { @@ -52,16 +52,16 @@ public class JpaRestfulServer extends RestfulServer { * ResourceProviders are fetched from the Spring context */ FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion(); - List resourceProviders; + ResourceProviderFactory resourceProviders; Object systemProvider; if (fhirVersion == FhirVersionEnum.DSTU2) { - resourceProviders = appCtx.getBean("myResourceProvidersDstu2", List.class); + resourceProviders = appCtx.getBean("myResourceProvidersDstu2", ResourceProviderFactory.class); systemProvider = appCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class); } else if (fhirVersion == FhirVersionEnum.DSTU3) { - resourceProviders = appCtx.getBean("myResourceProvidersDstu3", List.class); + resourceProviders = appCtx.getBean("myResourceProvidersDstu3", ResourceProviderFactory.class); systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class); } else if (fhirVersion == FhirVersionEnum.R4) { - resourceProviders = appCtx.getBean("myResourceProvidersR4", List.class); + resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class); systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class); } else { throw new IllegalStateException(); @@ -69,7 +69,7 @@ public class JpaRestfulServer extends RestfulServer { setFhirContext(appCtx.getBean(FhirContext.class)); - registerProviders(resourceProviders); + registerProviders(resourceProviders.createProviders()); registerProvider(systemProvider); /* @@ -133,9 +133,20 @@ public class JpaRestfulServer extends RestfulServer { * HTML output when the request is detected to come from a * browser. */ - ResponseHighlighterInterceptor responseHighlighterInterceptor = appCtx.getBean(ResponseHighlighterInterceptor.class); + ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor(); + ; this.registerInterceptor(responseHighlighterInterceptor); + /* + * Add some logging for each request + */ + LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); + loggingInterceptor.setLoggerName(HapiProperties.getLoggerName()); + loggingInterceptor.setMessageFormat(HapiProperties.getLoggerFormat()); + loggingInterceptor.setErrorMessageFormat(HapiProperties.getLoggerErrorFormat()); + loggingInterceptor.setLogExceptions(HapiProperties.getLoggerLogExceptions()); + this.registerInterceptor(loggingInterceptor); + /* * If you are hosting this server at a specific DNS name, the server will try to * figure out the FHIR base URL based on what the web container tells it, but @@ -172,7 +183,7 @@ public class JpaRestfulServer extends RestfulServer { // Define your CORS configuration. This is an example // showing a typical setup. You should customize this // to your specific needs - if(HapiProperties.getCorsEnabled()) { + if (HapiProperties.getCorsEnabled()) { CorsConfiguration config = new CorsConfiguration(); config.addAllowedHeader("x-fhir-starter"); config.addAllowedHeader("Origin"); @@ -202,7 +213,7 @@ public class JpaRestfulServer extends RestfulServer { subscriptionInterceptorLoader.registerInterceptors(); // Subscription debug logging - InterceptorService interceptorService = (InterceptorService) appCtx.getBean("interceptorService"); + IInterceptorService interceptorService = appCtx.getBean(IInterceptorService.class); interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); }