diff --git a/pom.xml b/pom.xml index 20b3fc7..03ed67c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,8 +14,7 @@ ca.uhn.hapi.fhir hapi-fhir - - 5.4.0-PRE2-SNAPSHOT + 5.4.0 hapi-fhir-jpaserver-starter diff --git a/src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java b/src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java index b14c7c4..be17571 100644 --- a/src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/mdm/MdmConfig.java @@ -7,7 +7,7 @@ import ca.uhn.fhir.jpa.starter.AppProperties; import ca.uhn.fhir.mdm.api.IMdmSettings; import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator; import ca.uhn.fhir.mdm.rules.config.MdmSettings; -import ca.uhn.fhir.rest.server.util.ISearchParamRetriever; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -29,17 +29,17 @@ import java.io.IOException; @Import({MdmConsumerConfig.class, MdmSubmitterConfig.class}) public class MdmConfig { - @Bean - MdmRuleValidator mdmRuleValidator(FhirContext theFhirContext, ISearchParamRetriever theSearchParamRetriever) { - return new MdmRuleValidator(theFhirContext, theSearchParamRetriever); - } + @Bean + MdmRuleValidator mdmRuleValidator(FhirContext theFhirContext, ISearchParamRegistry theSearchParamRegistry) { + return new MdmRuleValidator(theFhirContext, theSearchParamRegistry); + } - @Bean - IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties) throws IOException { - DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); - Resource resource = resourceLoader.getResource("mdm-rules.json"); - String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); - return new MdmSettings(theMdmRuleValidator).setEnabled(appProperties.getMdm_enabled()).setScriptText(json); - } + @Bean + IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties) throws IOException { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); + Resource resource = resourceLoader.getResource("mdm-rules.json"); + String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); + return new MdmSettings(theMdmRuleValidator).setEnabled(appProperties.getMdm_enabled()).setScriptText(json); + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java b/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java index 95708e7..8573130 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.cql.common.provider.CqlProviderLoader; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.IInterceptorService; @@ -9,31 +10,38 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; -import ca.uhn.fhir.jpa.bulk.provider.BulkDataExportProvider; +import ca.uhn.fhir.jpa.bulk.export.provider.BulkDataExportProvider; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; -import ca.uhn.fhir.jpa.packages.PackageInstallOutcomeJson; import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; -import ca.uhn.fhir.jpa.provider.*; +import ca.uhn.fhir.jpa.provider.GraphQLProvider; +import ca.uhn.fhir.jpa.provider.IJpaSystemProvider; +import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider; +import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; +import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; +import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; -import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.narrative2.NullNarrativeGenerator; +import ca.uhn.fhir.rest.server.ApacheProxyAddressStrategy; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; -import ca.uhn.fhir.rest.server.ApacheProxyAddressStrategy; import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.interceptor.*; +import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; +import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor; +import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; +import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; import com.google.common.base.Strings; @@ -44,347 +52,334 @@ import org.springframework.http.HttpHeaders; import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; public class BaseJpaRestfulServer extends RestfulServer { - @Autowired - DaoRegistry daoRegistry; - - @Autowired - DaoConfig daoConfig; - - @Autowired - ISearchParamRegistry searchParamRegistry; - - @Autowired - IFhirSystemDao fhirSystemDao; - - @Autowired - ResourceProviderFactory resourceProviders; - - @Autowired - IJpaSystemProvider jpaSystemProvider; - - @Autowired - IInterceptorBroadcaster interceptorBroadcaster; - - @Autowired - DatabaseBackedPagingProvider databaseBackedPagingProvider; - - @Autowired - IInterceptorService interceptorService; - - @Autowired - IValidatorModule validatorModule; - - @Autowired - Optional graphQLProvider; - - @Autowired - BulkDataExportProvider bulkDataExportProvider; - - @Autowired - PartitionManagementProvider partitionManagementProvider; - - @Autowired - BinaryStorageInterceptor binaryStorageInterceptor; - - @Autowired - IPackageInstallerSvc packageInstallerSvc; - - @Autowired - AppProperties appProperties; - - @Autowired - ApplicationContext myApplicationContext; - - @Autowired(required = false) - IRepositoryValidationInterceptorFactory factory; - - // These are set only if the features are enabled - private CqlProviderLoader cqlProviderLoader; - - public BaseJpaRestfulServer() { - } - - private static final long serialVersionUID = 1L; - - @SuppressWarnings("unchecked") - @Override - protected void initialize() throws ServletException { - super.initialize(); - - /* - * Create a FhirContext object that uses the version of FHIR - * specified in the properties file. - */ - // Customize supported resource types - List supportedResourceTypes = appProperties.getSupported_resource_types(); - - if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) { - supportedResourceTypes.add("SearchParameter"); - daoRegistry.setSupportedResourceTypes(supportedResourceTypes); - } - - setFhirContext(fhirSystemDao.getContext()); - - registerProviders(resourceProviders.createProviders()); - registerProvider(jpaSystemProvider); - - /* - * The conformance provider exports the supported resources, search parameters, etc for - * this server. The JPA version adds resourceProviders counts to the exported statement, so it - * is a nice addition. - * - * You can also create your own subclass of the conformance provider if you need to - * provide further customization of your server's CapabilityStatement - */ - - FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion(); - if (fhirVersion == FhirVersionEnum.DSTU2) { - - JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, fhirSystemDao, - daoConfig); - confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); - setServerConformanceProvider(confProvider); - } else { - if (fhirVersion == FhirVersionEnum.DSTU3) { - - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, fhirSystemDao, - daoConfig, searchParamRegistry); - confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); - setServerConformanceProvider(confProvider); - } else if (fhirVersion == FhirVersionEnum.R4) { - - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, fhirSystemDao, - daoConfig, searchParamRegistry); - confProvider.setImplementationDescription("HAPI FHIR R4 Server"); - setServerConformanceProvider(confProvider); - } else if (fhirVersion == FhirVersionEnum.R5) { - - JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, fhirSystemDao, - daoConfig, searchParamRegistry); - confProvider.setImplementationDescription("HAPI FHIR R5 Server"); - setServerConformanceProvider(confProvider); - } else { - throw new IllegalStateException(); - } - } - - /* - * ETag Support - */ - - if (appProperties.getEtag_support_enabled() == false) - setETagSupport(ETagSupportEnum.DISABLED); - - - /* - * This server tries to dynamically generate narratives - */ - FhirContext ctx = getFhirContext(); - INarrativeGenerator theNarrativeGenerator = - appProperties.getNarrative_enabled() ? - new DefaultThymeleafNarrativeGenerator() : - new NullNarrativeGenerator(); - ctx.setNarrativeGenerator(theNarrativeGenerator); - - /* - * Default to JSON and pretty printing - */ - setDefaultPrettyPrint(appProperties.getDefault_pretty_print()); - - /* - * Default encoding - */ - setDefaultResponseEncoding(appProperties.getDefault_encoding()); - - /* - * This configures the server to page search results to and from - * the database, instead of only paging them to memory. This may mean - * a performance hit when performing searches that return lots of results, - * but makes the server much more scalable. - */ - - setPagingProvider(databaseBackedPagingProvider); - - /* - * This interceptor formats the output using nice colourful - * HTML output when the request is detected to come from a - * browser. - */ - ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor(); - this.registerInterceptor(responseHighlighterInterceptor); - - if (appProperties.getFhirpath_interceptor_enabled()) { - registerInterceptor(new FhirPathFilterInterceptor()); - } - - /* - * Add some logging for each request - */ - LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); - loggingInterceptor.setLoggerName(appProperties.getLogger().getName()); - loggingInterceptor.setMessageFormat(appProperties.getLogger().getFormat()); - loggingInterceptor.setErrorMessageFormat(appProperties.getLogger().getError_format()); - loggingInterceptor.setLogExceptions(appProperties.getLogger().getLog_exceptions()); - 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 - * this doesn't always work. If you are setting links in your search bundles that - * just refer to "localhost", you might want to use a server address strategy: - */ - String serverAddress = appProperties.getServer_address(); - if (!Strings.isNullOrEmpty(serverAddress)) { - setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); - } else if (appProperties.getUse_apache_address_strategy()) { - boolean useHttps = appProperties.getUse_apache_address_strategy_https(); - setServerAddressStrategy(useHttps ? ApacheProxyAddressStrategy.forHttps() : - ApacheProxyAddressStrategy.forHttp()); - } else { - setServerAddressStrategy(new IncomingRequestAddressStrategy()); - } - - /* - * If you are using DSTU3+, you may want to add a terminology uploader, which allows - * uploading of external terminologies such as Snomed CT. Note that this uploader - * does not have any security attached (any anonymous user may use it by default) - * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor - * with this feature. - */ - if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { // <-- ENABLED RIGHT NOW - registerProvider(myApplicationContext.getBean(TerminologyUploaderProvider.class)); - } - - // If you want to enable the $trigger-subscription operation to allow - // manual triggering of a subscription delivery, enable this provider - if (true) { // <-- ENABLED RIGHT NOW - registerProvider(myApplicationContext.getBean(SubscriptionTriggeringProvider.class)); - } - - // Define your CORS configuration. This is an example - // showing a typical setup. You should customize this - // to your specific needs - if (appProperties.getCors() != null) { - CorsConfiguration config = new CorsConfiguration(); - config.addAllowedHeader(HttpHeaders.ORIGIN); - config.addAllowedHeader(HttpHeaders.ACCEPT); - config.addAllowedHeader(HttpHeaders.CONTENT_TYPE); - config.addAllowedHeader(HttpHeaders.AUTHORIZATION); - config.addAllowedHeader(HttpHeaders.CACHE_CONTROL); - config.addAllowedHeader("x-fhir-starter"); - config.addAllowedHeader("X-Requested-With"); - config.addAllowedHeader("Prefer"); - List allAllowedCORSOrigins = appProperties.getCors().getAllowed_origin(); - allAllowedCORSOrigins.forEach(config::addAllowedOrigin); - - - config.addExposedHeader("Location"); - config.addExposedHeader("Content-Location"); - config.setAllowedMethods( - Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD")); - config.setAllowCredentials(appProperties.getCors().getAllow_Credentials()); - - // Create the interceptor and register it - CorsInterceptor interceptor = new CorsInterceptor(config); - registerInterceptor(interceptor); - } - - // If subscriptions are enabled, we want to register the interceptor that - // will activate them and match results against them - if (appProperties.getSubscription() != null) { - // Subscription debug logging - interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); - } - - // Cascading deletes - - - if (appProperties.getAllow_cascading_deletes()) { - CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, - daoRegistry, interceptorBroadcaster); - getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); - } - - // Binary Storage - if (appProperties.getBinary_storage_enabled()) { - getInterceptorService().registerInterceptor(binaryStorageInterceptor); - } - - // Validation - - if (validatorModule != null) { - if (appProperties.getValidation().getRequests_enabled()) { - RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); - interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); - interceptor.setValidatorModules(Collections.singletonList(validatorModule)); - registerInterceptor(interceptor); - } - if (appProperties.getValidation().getResponses_enabled()) { - ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor(); - interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); - interceptor.setValidatorModules(Collections.singletonList(validatorModule)); - registerInterceptor(interceptor); - } - } - - // GraphQL - if (appProperties.getGraphql_enabled()) { - if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { - registerProvider(graphQLProvider.get()); - } - } - - if (appProperties.getAllowed_bundle_types() != null) { - daoConfig.setBundleTypesAllowedForStorage(appProperties.getAllowed_bundle_types().stream().map(BundleType::toCode).collect(Collectors.toSet())); - } - - daoConfig.setDeferIndexingForCodesystemsOfSize(appProperties.getDefer_indexing_for_codesystems_of_size()); - - // Bulk Export - if (appProperties.getBulk_export_enabled()) { - registerProvider(bulkDataExportProvider); - } - - // Partitioning - if (appProperties.getPartitioning() != null) { - registerInterceptor(new RequestTenantPartitionInterceptor()); - setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); - registerProviders(partitionManagementProvider); - } - - if (appProperties.getClient_id_strategy() == DaoConfig.ClientIdStrategyEnum.ANY) { - daoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); - daoConfig.setResourceClientIdStrategy(appProperties.getClient_id_strategy()); - } - - if (appProperties.getImplementationGuides() != null) { - Map guides = appProperties.getImplementationGuides(); - for (Map.Entry guide : guides.entrySet()) { - packageInstallerSvc.install(new PackageInstallationSpec() - .setPackageUrl(guide.getValue().getUrl()) - .setName(guide.getValue().getName()) - .setVersion(guide.getValue().getVersion()) - .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); - - } - } - - if(factory != null) { - interceptorService.registerInterceptor(factory.buildUsingStoredStructureDefinitions()); - } - - - if (appProperties.getLastn_enabled()) { - daoConfig.setLastNEnabled(true); - } - - daoConfig.getModelConfig().setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level()); - - daoConfig.getModelConfig().setIndexOnContainedResources(appProperties.getEnable_index_contained_resource()); - } + private static final long serialVersionUID = 1L; + @Autowired + DaoRegistry daoRegistry; + @Autowired + DaoConfig daoConfig; + @Autowired + ISearchParamRegistry searchParamRegistry; + @Autowired + IFhirSystemDao fhirSystemDao; + @Autowired + ResourceProviderFactory resourceProviders; + @Autowired + IJpaSystemProvider jpaSystemProvider; + @Autowired + IInterceptorBroadcaster interceptorBroadcaster; + @Autowired + DatabaseBackedPagingProvider databaseBackedPagingProvider; + @Autowired + IInterceptorService interceptorService; + @Autowired + IValidatorModule validatorModule; + @Autowired + Optional graphQLProvider; + @Autowired + BulkDataExportProvider bulkDataExportProvider; + @Autowired + PartitionManagementProvider partitionManagementProvider; + @Autowired + BinaryStorageInterceptor binaryStorageInterceptor; + @Autowired + IPackageInstallerSvc packageInstallerSvc; + @Autowired + AppProperties appProperties; + @Autowired + ApplicationContext myApplicationContext; + @Autowired(required = false) + IRepositoryValidationInterceptorFactory factory; + // These are set only if the features are enabled + private CqlProviderLoader cqlProviderLoader; + @Autowired + private IValidationSupport myValidationSupport; + + public BaseJpaRestfulServer() { + } + + @SuppressWarnings("unchecked") + @Override + protected void initialize() throws ServletException { + super.initialize(); + + /* + * Create a FhirContext object that uses the version of FHIR + * specified in the properties file. + */ + // Customize supported resource types + List supportedResourceTypes = appProperties.getSupported_resource_types(); + + if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) { + supportedResourceTypes.add("SearchParameter"); + daoRegistry.setSupportedResourceTypes(supportedResourceTypes); + } + + setFhirContext(fhirSystemDao.getContext()); + + registerProviders(resourceProviders.createProviders()); + registerProvider(jpaSystemProvider); + + /* + * The conformance provider exports the supported resources, search parameters, etc for + * this server. The JPA version adds resourceProviders counts to the exported statement, so it + * is a nice addition. + * + * You can also create your own subclass of the conformance provider if you need to + * provide further customization of your server's CapabilityStatement + */ + + FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion(); + if (fhirVersion == FhirVersionEnum.DSTU2) { + + JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, fhirSystemDao, + daoConfig); + confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); + setServerConformanceProvider(confProvider); + } else { + if (fhirVersion == FhirVersionEnum.DSTU3) { + + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, fhirSystemDao, + daoConfig, searchParamRegistry); + confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); + setServerConformanceProvider(confProvider); + } else if (fhirVersion == FhirVersionEnum.R4) { + + JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, fhirSystemDao, + daoConfig, searchParamRegistry, myValidationSupport); + confProvider.setImplementationDescription("HAPI FHIR R4 Server"); + setServerConformanceProvider(confProvider); + } else if (fhirVersion == FhirVersionEnum.R5) { + + JpaCapabilityStatementProvider confProvider = new JpaCapabilityStatementProvider(this, fhirSystemDao, + daoConfig, searchParamRegistry, myValidationSupport); + confProvider.setImplementationDescription("HAPI FHIR R5 Server"); + setServerConformanceProvider(confProvider); + } else { + throw new IllegalStateException(); + } + } + + /* + * ETag Support + */ + + if (appProperties.getEtag_support_enabled() == false) + setETagSupport(ETagSupportEnum.DISABLED); + + + /* + * This server tries to dynamically generate narratives + */ + FhirContext ctx = getFhirContext(); + INarrativeGenerator theNarrativeGenerator = + appProperties.getNarrative_enabled() ? + new DefaultThymeleafNarrativeGenerator() : + new NullNarrativeGenerator(); + ctx.setNarrativeGenerator(theNarrativeGenerator); + + /* + * Default to JSON and pretty printing + */ + setDefaultPrettyPrint(appProperties.getDefault_pretty_print()); + + /* + * Default encoding + */ + setDefaultResponseEncoding(appProperties.getDefault_encoding()); + + /* + * This configures the server to page search results to and from + * the database, instead of only paging them to memory. This may mean + * a performance hit when performing searches that return lots of results, + * but makes the server much more scalable. + */ + + setPagingProvider(databaseBackedPagingProvider); + + /* + * This interceptor formats the output using nice colourful + * HTML output when the request is detected to come from a + * browser. + */ + ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor(); + this.registerInterceptor(responseHighlighterInterceptor); + + if (appProperties.getFhirpath_interceptor_enabled()) { + registerInterceptor(new FhirPathFilterInterceptor()); + } + + /* + * Add some logging for each request + */ + LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); + loggingInterceptor.setLoggerName(appProperties.getLogger().getName()); + loggingInterceptor.setMessageFormat(appProperties.getLogger().getFormat()); + loggingInterceptor.setErrorMessageFormat(appProperties.getLogger().getError_format()); + loggingInterceptor.setLogExceptions(appProperties.getLogger().getLog_exceptions()); + 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 + * this doesn't always work. If you are setting links in your search bundles that + * just refer to "localhost", you might want to use a server address strategy: + */ + String serverAddress = appProperties.getServer_address(); + if (!Strings.isNullOrEmpty(serverAddress)) { + setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); + } else if (appProperties.getUse_apache_address_strategy()) { + boolean useHttps = appProperties.getUse_apache_address_strategy_https(); + setServerAddressStrategy(useHttps ? ApacheProxyAddressStrategy.forHttps() : + ApacheProxyAddressStrategy.forHttp()); + } else { + setServerAddressStrategy(new IncomingRequestAddressStrategy()); + } + + /* + * If you are using DSTU3+, you may want to add a terminology uploader, which allows + * uploading of external terminologies such as Snomed CT. Note that this uploader + * does not have any security attached (any anonymous user may use it by default) + * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor + * with this feature. + */ + if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { // <-- ENABLED RIGHT NOW + registerProvider(myApplicationContext.getBean(TerminologyUploaderProvider.class)); + } + + // If you want to enable the $trigger-subscription operation to allow + // manual triggering of a subscription delivery, enable this provider + if (true) { // <-- ENABLED RIGHT NOW + registerProvider(myApplicationContext.getBean(SubscriptionTriggeringProvider.class)); + } + + // Define your CORS configuration. This is an example + // showing a typical setup. You should customize this + // to your specific needs + if (appProperties.getCors() != null) { + CorsConfiguration config = new CorsConfiguration(); + config.addAllowedHeader(HttpHeaders.ORIGIN); + config.addAllowedHeader(HttpHeaders.ACCEPT); + config.addAllowedHeader(HttpHeaders.CONTENT_TYPE); + config.addAllowedHeader(HttpHeaders.AUTHORIZATION); + config.addAllowedHeader(HttpHeaders.CACHE_CONTROL); + config.addAllowedHeader("x-fhir-starter"); + config.addAllowedHeader("X-Requested-With"); + config.addAllowedHeader("Prefer"); + List allAllowedCORSOrigins = appProperties.getCors().getAllowed_origin(); + allAllowedCORSOrigins.forEach(config::addAllowedOrigin); + + + config.addExposedHeader("Location"); + config.addExposedHeader("Content-Location"); + config.setAllowedMethods( + Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD")); + config.setAllowCredentials(appProperties.getCors().getAllow_Credentials()); + + // Create the interceptor and register it + CorsInterceptor interceptor = new CorsInterceptor(config); + registerInterceptor(interceptor); + } + + // If subscriptions are enabled, we want to register the interceptor that + // will activate them and match results against them + if (appProperties.getSubscription() != null) { + // Subscription debug logging + interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); + } + + // Cascading deletes + + + if (appProperties.getAllow_cascading_deletes()) { + CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, + daoRegistry, interceptorBroadcaster); + getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); + } + + // Binary Storage + if (appProperties.getBinary_storage_enabled()) { + getInterceptorService().registerInterceptor(binaryStorageInterceptor); + } + + // Validation + + if (validatorModule != null) { + if (appProperties.getValidation().getRequests_enabled()) { + RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); + interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + interceptor.setValidatorModules(Collections.singletonList(validatorModule)); + registerInterceptor(interceptor); + } + if (appProperties.getValidation().getResponses_enabled()) { + ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor(); + interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); + interceptor.setValidatorModules(Collections.singletonList(validatorModule)); + registerInterceptor(interceptor); + } + } + + // GraphQL + if (appProperties.getGraphql_enabled()) { + if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { + registerProvider(graphQLProvider.get()); + } + } + + if (appProperties.getAllowed_bundle_types() != null) { + daoConfig.setBundleTypesAllowedForStorage(appProperties.getAllowed_bundle_types().stream().map(BundleType::toCode).collect(Collectors.toSet())); + } + + daoConfig.setDeferIndexingForCodesystemsOfSize(appProperties.getDefer_indexing_for_codesystems_of_size()); + + // Bulk Export + if (appProperties.getBulk_export_enabled()) { + registerProvider(bulkDataExportProvider); + } + + // Partitioning + if (appProperties.getPartitioning() != null) { + registerInterceptor(new RequestTenantPartitionInterceptor()); + setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); + registerProviders(partitionManagementProvider); + } + + if (appProperties.getClient_id_strategy() == DaoConfig.ClientIdStrategyEnum.ANY) { + daoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); + daoConfig.setResourceClientIdStrategy(appProperties.getClient_id_strategy()); + } + + if (appProperties.getImplementationGuides() != null) { + Map guides = appProperties.getImplementationGuides(); + for (Map.Entry guide : guides.entrySet()) { + packageInstallerSvc.install(new PackageInstallationSpec() + .setPackageUrl(guide.getValue().getUrl()) + .setName(guide.getValue().getName()) + .setVersion(guide.getValue().getVersion()) + .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); + + } + } + + if (factory != null) { + interceptorService.registerInterceptor(factory.buildUsingStoredStructureDefinitions()); + } + + + if (appProperties.getLastn_enabled()) { + daoConfig.setLastNEnabled(true); + } + + daoConfig.getModelConfig().setNormalizedQuantitySearchLevel(appProperties.getNormalized_quantity_search_level()); + + daoConfig.getModelConfig().setIndexOnContainedResources(appProperties.getEnable_index_contained_resource()); + } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java index 52ffb6f..56b7316 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java @@ -67,16 +67,18 @@ public class ExampleServerR5IT { public void testWebsocketSubscription() throws Exception { /* - * Create topic + * Create topic (will be contained in subscription) */ SubscriptionTopic topic = new SubscriptionTopic(); - topic.getResourceTrigger().getQueryCriteria().setCurrent("Observation?status=final"); + topic.setId("#1"); + topic.getResourceTriggerFirstRep().getQueryCriteria().setCurrent("Observation?status=final"); /* * Create subscription */ Subscription subscription = new Subscription(); - subscription.getTopic().setResource(topic); + subscription.getContained().add(topic); + subscription.setTopic("#1"); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); subscription.setStatus(Enumerations.SubscriptionState.REQUESTED); subscription.getChannelType()