Merge pull request #51 from hapifhir/allowedBundleTypes
Allowed bundle types
This commit is contained in:
5
.editorconfig
Normal file
5
.editorconfig
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[*.java]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
138
.gitignore
vendored
138
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
target/
|
|
||||||
.idea/
|
|
||||||
*.iml
|
|
||||||
*.orig
|
*.orig
|
||||||
|
target/
|
||||||
|
*.iml
|
||||||
|
|
||||||
# Compiled class file
|
# Compiled class file
|
||||||
*.class
|
*.class
|
||||||
@@ -26,3 +25,136 @@ target/
|
|||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
|
### Windows template
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
### JetBrains template
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
### Eclipse template
|
||||||
|
.metadata
|
||||||
|
bin/
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
|
*.swp
|
||||||
|
*~.nib
|
||||||
|
local.properties
|
||||||
|
.settings/
|
||||||
|
.loadpath
|
||||||
|
.recommenders
|
||||||
|
|
||||||
|
# External tool builders
|
||||||
|
.externalToolBuilders/
|
||||||
|
|
||||||
|
# Locally stored "Eclipse launch configurations"
|
||||||
|
*.launch
|
||||||
|
|
||||||
|
# PyDev specific (Python IDE for Eclipse)
|
||||||
|
*.pydevproject
|
||||||
|
|
||||||
|
# CDT-specific (C/C++ Development Tooling)
|
||||||
|
.cproject
|
||||||
|
|
||||||
|
# CDT- autotools
|
||||||
|
.autotools
|
||||||
|
|
||||||
|
# Java annotation processor (APT)
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
# PDT-specific (PHP Development Tools)
|
||||||
|
.buildpath
|
||||||
|
|
||||||
|
# sbteclipse plugin
|
||||||
|
.target
|
||||||
|
|
||||||
|
# Tern plugin
|
||||||
|
.tern-project
|
||||||
|
|
||||||
|
# TeXlipse plugin
|
||||||
|
.texlipse
|
||||||
|
|
||||||
|
# STS (Spring Tool Suite)
|
||||||
|
.springBeans
|
||||||
|
|
||||||
|
# Code Recommenders
|
||||||
|
.recommenders/
|
||||||
|
|
||||||
|
# Annotation Processing
|
||||||
|
.apt_generated/
|
||||||
|
|
||||||
|
# Scala IDE specific (Scala & Java development for Eclipse)
|
||||||
|
.cache-main
|
||||||
|
.scala_dependencies
|
||||||
|
.worksheet
|
||||||
|
|
||||||
|
### macOS template
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Icon must end with two \r
|
||||||
|
Icon
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
@@ -45,6 +45,7 @@ public class HapiProperties {
|
|||||||
static final String SUBSCRIPTION_EMAIL_ENABLED = "subscription.email.enabled";
|
static final String SUBSCRIPTION_EMAIL_ENABLED = "subscription.email.enabled";
|
||||||
static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled";
|
static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled";
|
||||||
static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled";
|
static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled";
|
||||||
|
static final String ALLOWED_BUNDLE_TYPES = "allowed_bundle_types";
|
||||||
static final String TEST_PORT = "test.port";
|
static final String TEST_PORT = "test.port";
|
||||||
static final String TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS = "tester.config.refuse_to_fetch_third_party_urls";
|
static final String TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS = "tester.config.refuse_to_fetch_third_party_urls";
|
||||||
static final String CORS_ENABLED = "cors.enabled";
|
static final String CORS_ENABLED = "cors.enabled";
|
||||||
@@ -286,6 +287,10 @@ public class HapiProperties {
|
|||||||
return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*");
|
return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getAllowedBundleTypes() {
|
||||||
|
return HapiProperties.getProperty(ALLOWED_BUNDLE_TYPES, "");
|
||||||
|
}
|
||||||
|
|
||||||
public static Set<String> getSupportedResourceTypes() {
|
public static Set<String> getSupportedResourceTypes() {
|
||||||
String[] types = defaultString(getProperty("supported_resource_types")).split(",");
|
String[] types = defaultString(getProperty("supported_resource_types")).split(",");
|
||||||
return Arrays.stream(types)
|
return Arrays.stream(types)
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ import ca.uhn.fhir.rest.server.RestfulServer;
|
|||||||
import ca.uhn.fhir.rest.server.interceptor.*;
|
import ca.uhn.fhir.rest.server.interceptor.*;
|
||||||
import ca.uhn.fhir.validation.IValidatorModule;
|
import ca.uhn.fhir.validation.IValidatorModule;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
import org.hl7.fhir.dstu3.model.Bundle;
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.Bundle.BundleType;
|
||||||
import org.hl7.fhir.dstu3.model.Meta;
|
import org.hl7.fhir.dstu3.model.Meta;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
@@ -40,258 +43,281 @@ import java.util.Set;
|
|||||||
|
|
||||||
public class JpaRestfulServer extends RestfulServer {
|
public class JpaRestfulServer extends RestfulServer {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() throws ServletException {
|
protected void initialize() throws ServletException {
|
||||||
super.initialize();
|
super.initialize();
|
||||||
|
|
||||||
/*
|
|
||||||
* Create a FhirContext object that uses the version of FHIR
|
|
||||||
* specified in the properties file.
|
|
||||||
*/
|
|
||||||
ApplicationContext appCtx = (ApplicationContext) getServletContext().getAttribute("org.springframework.web.context.WebApplicationContext.ROOT");
|
|
||||||
// Customize supported resource types
|
|
||||||
Set<String> supportedResourceTypes = HapiProperties.getSupportedResourceTypes();
|
|
||||||
if (!supportedResourceTypes.isEmpty()) {
|
|
||||||
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
|
|
||||||
daoRegistry.setSupportedResourceTypes(supportedResourceTypes);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ResourceProviders are fetched from the Spring context
|
|
||||||
*/
|
|
||||||
FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion();
|
|
||||||
ResourceProviderFactory resourceProviders;
|
|
||||||
Object systemProvider;
|
|
||||||
if (fhirVersion == FhirVersionEnum.DSTU2) {
|
|
||||||
resourceProviders = appCtx.getBean("myResourceProvidersDstu2", ResourceProviderFactory.class);
|
|
||||||
systemProvider = appCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
|
||||||
resourceProviders = appCtx.getBean("myResourceProvidersDstu3", ResourceProviderFactory.class);
|
|
||||||
systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.R4) {
|
|
||||||
resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class);
|
|
||||||
systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.R5) {
|
|
||||||
resourceProviders = appCtx.getBean("myResourceProvidersR5", ResourceProviderFactory.class);
|
|
||||||
systemProvider = appCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
|
|
||||||
setFhirContext(appCtx.getBean(FhirContext.class));
|
|
||||||
|
|
||||||
registerProviders(resourceProviders.createProviders());
|
|
||||||
registerProvider(systemProvider);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
if (fhirVersion == FhirVersionEnum.DSTU2) {
|
|
||||||
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = appCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
|
||||||
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, appCtx.getBean(DaoConfig.class));
|
|
||||||
confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server");
|
|
||||||
setServerConformanceProvider(confProvider);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
|
||||||
IFhirSystemDao<Bundle, Meta> systemDao = appCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
|
||||||
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, appCtx.getBean(DaoConfig.class));
|
|
||||||
confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server");
|
|
||||||
setServerConformanceProvider(confProvider);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.R4) {
|
|
||||||
IFhirSystemDao<org.hl7.fhir.r4.model.Bundle, org.hl7.fhir.r4.model.Meta> systemDao = appCtx.getBean("mySystemDaoR4", IFhirSystemDao.class);
|
|
||||||
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, appCtx.getBean(DaoConfig.class));
|
|
||||||
confProvider.setImplementationDescription("HAPI FHIR R4 Server");
|
|
||||||
setServerConformanceProvider(confProvider);
|
|
||||||
} else if (fhirVersion == FhirVersionEnum.R5) {
|
|
||||||
IFhirSystemDao<org.hl7.fhir.r5.model.Bundle, org.hl7.fhir.r5.model.Meta> systemDao = appCtx.getBean("mySystemDaoR5", IFhirSystemDao.class);
|
|
||||||
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, appCtx.getBean(DaoConfig.class));
|
|
||||||
confProvider.setImplementationDescription("HAPI FHIR R5 Server");
|
|
||||||
setServerConformanceProvider(confProvider);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ETag Support
|
|
||||||
*/
|
|
||||||
setETagSupport(HapiProperties.getEtagSupport());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This server tries to dynamically generate narratives
|
|
||||||
*/
|
|
||||||
FhirContext ctx = getFhirContext();
|
|
||||||
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default to JSON and pretty printing
|
|
||||||
*/
|
|
||||||
setDefaultPrettyPrint(HapiProperties.getDefaultPrettyPrint());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Default encoding
|
|
||||||
*/
|
|
||||||
setDefaultResponseEncoding(HapiProperties.getDefaultEncoding());
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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(appCtx.getBean(DatabaseBackedPagingProvider.class));
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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
|
|
||||||
* 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 = HapiProperties.getServerAddress();
|
|
||||||
if (serverAddress != null && serverAddress.length() > 0) {
|
|
||||||
setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 (false) { // <-- DISABLED RIGHT NOW
|
|
||||||
registerProvider(appCtx.getBean(TerminologyUploaderProvider.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If you want to enable the $trigger-subscription operation to allow
|
|
||||||
// manual triggering of a subscription delivery, enable this provider
|
|
||||||
if (false) { // <-- DISABLED RIGHT NOW
|
|
||||||
SubscriptionTriggeringProvider retriggeringProvider = appCtx.getBean(SubscriptionTriggeringProvider.class);
|
|
||||||
registerProvider(retriggeringProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define your CORS configuration. This is an example
|
|
||||||
// showing a typical setup. You should customize this
|
|
||||||
// to your specific needs
|
|
||||||
if (HapiProperties.getCorsEnabled()) {
|
|
||||||
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");
|
|
||||||
String allAllowedCORSOrigins = HapiProperties.getCorsAllowedOrigin();
|
|
||||||
Arrays.stream(allAllowedCORSOrigins.split(",")).forEach(o -> {
|
|
||||||
config.addAllowedOrigin(o);
|
|
||||||
});
|
|
||||||
config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin());
|
|
||||||
|
|
||||||
config.addExposedHeader("Location");
|
|
||||||
config.addExposedHeader("Content-Location");
|
|
||||||
config.setAllowedMethods(
|
|
||||||
Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"));
|
|
||||||
config.setAllowCredentials(HapiProperties.getCorsAllowedCredentials());
|
|
||||||
|
|
||||||
// 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 (HapiProperties.getSubscriptionWebsocketEnabled() ||
|
|
||||||
HapiProperties.getSubscriptionEmailEnabled() ||
|
|
||||||
HapiProperties.getSubscriptionRestHookEnabled()) {
|
|
||||||
// Loads subscription interceptors (SubscriptionActivatingInterceptor, SubscriptionMatcherInterceptor)
|
|
||||||
// with activation of scheduled subscription
|
|
||||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = appCtx.getBean(SubscriptionInterceptorLoader.class);
|
|
||||||
subscriptionInterceptorLoader.registerInterceptors();
|
|
||||||
|
|
||||||
// Subscription debug logging
|
|
||||||
IInterceptorService interceptorService = appCtx.getBean(IInterceptorService.class);
|
|
||||||
interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cascading deletes
|
|
||||||
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
|
|
||||||
IInterceptorBroadcaster interceptorBroadcaster = appCtx.getBean(IInterceptorBroadcaster.class);
|
|
||||||
if (HapiProperties.getAllowCascadingDeletes()) {
|
|
||||||
CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(daoRegistry, interceptorBroadcaster);
|
|
||||||
getInterceptorService().registerInterceptor(cascadingDeleteInterceptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Binary Storage
|
|
||||||
if (HapiProperties.isBinaryStorageEnabled()) {
|
|
||||||
BinaryStorageInterceptor binaryStorageInterceptor = appCtx.getBean(BinaryStorageInterceptor.class);
|
|
||||||
getInterceptorService().registerInterceptor(binaryStorageInterceptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
IValidatorModule validatorModule;
|
|
||||||
switch (fhirVersion) {
|
|
||||||
case DSTU3:
|
|
||||||
validatorModule = appCtx.getBean("myInstanceValidatorDstu3", IValidatorModule.class);
|
|
||||||
break;
|
|
||||||
case R4:
|
|
||||||
validatorModule = appCtx.getBean("myInstanceValidatorR4", IValidatorModule.class);
|
|
||||||
break;
|
|
||||||
case R5:
|
|
||||||
validatorModule = appCtx.getBean("myInstanceValidatorR5", IValidatorModule.class);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
validatorModule = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (validatorModule != null) {
|
|
||||||
if (HapiProperties.getValidateRequestsEnabled()) {
|
|
||||||
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
|
|
||||||
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
|
|
||||||
interceptor.setValidatorModules(Collections.singletonList(validatorModule));
|
|
||||||
registerInterceptor(interceptor);
|
|
||||||
}
|
|
||||||
if (HapiProperties.getValidateResponsesEnabled()) {
|
|
||||||
ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor();
|
|
||||||
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
|
|
||||||
interceptor.setValidatorModules(Collections.singletonList(validatorModule));
|
|
||||||
registerInterceptor(interceptor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GraphQL
|
|
||||||
if (HapiProperties.getGraphqlEnabled()) {
|
|
||||||
if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
|
||||||
registerProvider(appCtx.getBean(GraphQLProvider.class));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a FhirContext object that uses the version of FHIR
|
||||||
|
* specified in the properties file.
|
||||||
|
*/
|
||||||
|
ApplicationContext appCtx = (ApplicationContext) getServletContext()
|
||||||
|
.getAttribute("org.springframework.web.context.WebApplicationContext.ROOT");
|
||||||
|
// Customize supported resource types
|
||||||
|
Set<String> supportedResourceTypes = HapiProperties.getSupportedResourceTypes();
|
||||||
|
if (!supportedResourceTypes.isEmpty()) {
|
||||||
|
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
|
||||||
|
daoRegistry.setSupportedResourceTypes(supportedResourceTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ResourceProviders are fetched from the Spring context
|
||||||
|
*/
|
||||||
|
FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion();
|
||||||
|
ResourceProviderFactory resourceProviders;
|
||||||
|
Object systemProvider;
|
||||||
|
if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||||
|
resourceProviders = appCtx.getBean("myResourceProvidersDstu2", ResourceProviderFactory.class);
|
||||||
|
systemProvider = appCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||||
|
resourceProviders = appCtx.getBean("myResourceProvidersDstu3", ResourceProviderFactory.class);
|
||||||
|
systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.R4) {
|
||||||
|
resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class);
|
||||||
|
systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.R5) {
|
||||||
|
resourceProviders = appCtx.getBean("myResourceProvidersR5", ResourceProviderFactory.class);
|
||||||
|
systemProvider = appCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
setFhirContext(appCtx.getBean(FhirContext.class));
|
||||||
|
|
||||||
|
registerProviders(resourceProviders.createProviders());
|
||||||
|
registerProvider(systemProvider);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
if (fhirVersion == FhirVersionEnum.DSTU2) {
|
||||||
|
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = appCtx
|
||||||
|
.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
|
||||||
|
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao,
|
||||||
|
appCtx.getBean(DaoConfig.class));
|
||||||
|
confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server");
|
||||||
|
setServerConformanceProvider(confProvider);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
|
||||||
|
IFhirSystemDao<Bundle, Meta> systemDao = appCtx
|
||||||
|
.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
|
||||||
|
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao,
|
||||||
|
appCtx.getBean(DaoConfig.class));
|
||||||
|
confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server");
|
||||||
|
setServerConformanceProvider(confProvider);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.R4) {
|
||||||
|
IFhirSystemDao<org.hl7.fhir.r4.model.Bundle, org.hl7.fhir.r4.model.Meta> systemDao = appCtx
|
||||||
|
.getBean("mySystemDaoR4", IFhirSystemDao.class);
|
||||||
|
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao,
|
||||||
|
appCtx.getBean(DaoConfig.class));
|
||||||
|
confProvider.setImplementationDescription("HAPI FHIR R4 Server");
|
||||||
|
setServerConformanceProvider(confProvider);
|
||||||
|
} else if (fhirVersion == FhirVersionEnum.R5) {
|
||||||
|
IFhirSystemDao<org.hl7.fhir.r5.model.Bundle, org.hl7.fhir.r5.model.Meta> systemDao = appCtx
|
||||||
|
.getBean("mySystemDaoR5", IFhirSystemDao.class);
|
||||||
|
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao,
|
||||||
|
appCtx.getBean(DaoConfig.class));
|
||||||
|
confProvider.setImplementationDescription("HAPI FHIR R5 Server");
|
||||||
|
setServerConformanceProvider(confProvider);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ETag Support
|
||||||
|
*/
|
||||||
|
setETagSupport(HapiProperties.getEtagSupport());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This server tries to dynamically generate narratives
|
||||||
|
*/
|
||||||
|
FhirContext ctx = getFhirContext();
|
||||||
|
ctx.setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default to JSON and pretty printing
|
||||||
|
*/
|
||||||
|
setDefaultPrettyPrint(HapiProperties.getDefaultPrettyPrint());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Default encoding
|
||||||
|
*/
|
||||||
|
setDefaultResponseEncoding(HapiProperties.getDefaultEncoding());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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(appCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
* 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 = HapiProperties.getServerAddress();
|
||||||
|
if (serverAddress != null && serverAddress.length() > 0) {
|
||||||
|
setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 (false) { // <-- DISABLED RIGHT NOW
|
||||||
|
registerProvider(appCtx.getBean(TerminologyUploaderProvider.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you want to enable the $trigger-subscription operation to allow
|
||||||
|
// manual triggering of a subscription delivery, enable this provider
|
||||||
|
if (false) { // <-- DISABLED RIGHT NOW
|
||||||
|
SubscriptionTriggeringProvider retriggeringProvider = appCtx
|
||||||
|
.getBean(SubscriptionTriggeringProvider.class);
|
||||||
|
registerProvider(retriggeringProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define your CORS configuration. This is an example
|
||||||
|
// showing a typical setup. You should customize this
|
||||||
|
// to your specific needs
|
||||||
|
if (HapiProperties.getCorsEnabled()) {
|
||||||
|
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");
|
||||||
|
String allAllowedCORSOrigins = HapiProperties.getCorsAllowedOrigin();
|
||||||
|
Arrays.stream(allAllowedCORSOrigins.split(",")).forEach(o -> {
|
||||||
|
config.addAllowedOrigin(o);
|
||||||
|
});
|
||||||
|
config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin());
|
||||||
|
|
||||||
|
config.addExposedHeader("Location");
|
||||||
|
config.addExposedHeader("Content-Location");
|
||||||
|
config.setAllowedMethods(
|
||||||
|
Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"));
|
||||||
|
config.setAllowCredentials(HapiProperties.getCorsAllowedCredentials());
|
||||||
|
|
||||||
|
// 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 (HapiProperties.getSubscriptionWebsocketEnabled() ||
|
||||||
|
HapiProperties.getSubscriptionEmailEnabled() ||
|
||||||
|
HapiProperties.getSubscriptionRestHookEnabled()) {
|
||||||
|
// Loads subscription interceptors (SubscriptionActivatingInterceptor, SubscriptionMatcherInterceptor)
|
||||||
|
// with activation of scheduled subscription
|
||||||
|
SubscriptionInterceptorLoader subscriptionInterceptorLoader = appCtx
|
||||||
|
.getBean(SubscriptionInterceptorLoader.class);
|
||||||
|
subscriptionInterceptorLoader.registerInterceptors();
|
||||||
|
|
||||||
|
// Subscription debug logging
|
||||||
|
IInterceptorService interceptorService = appCtx.getBean(IInterceptorService.class);
|
||||||
|
interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cascading deletes
|
||||||
|
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
|
||||||
|
IInterceptorBroadcaster interceptorBroadcaster = appCtx.getBean(IInterceptorBroadcaster.class);
|
||||||
|
if (HapiProperties.getAllowCascadingDeletes()) {
|
||||||
|
CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(
|
||||||
|
daoRegistry, interceptorBroadcaster);
|
||||||
|
getInterceptorService().registerInterceptor(cascadingDeleteInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary Storage
|
||||||
|
if (HapiProperties.isBinaryStorageEnabled()) {
|
||||||
|
BinaryStorageInterceptor binaryStorageInterceptor = appCtx
|
||||||
|
.getBean(BinaryStorageInterceptor.class);
|
||||||
|
getInterceptorService().registerInterceptor(binaryStorageInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
IValidatorModule validatorModule;
|
||||||
|
switch (fhirVersion) {
|
||||||
|
case DSTU3:
|
||||||
|
validatorModule = appCtx.getBean("myInstanceValidatorDstu3", IValidatorModule.class);
|
||||||
|
break;
|
||||||
|
case R4:
|
||||||
|
validatorModule = appCtx.getBean("myInstanceValidatorR4", IValidatorModule.class);
|
||||||
|
break;
|
||||||
|
case R5:
|
||||||
|
validatorModule = appCtx.getBean("myInstanceValidatorR5", IValidatorModule.class);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
validatorModule = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (validatorModule != null) {
|
||||||
|
if (HapiProperties.getValidateRequestsEnabled()) {
|
||||||
|
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
|
||||||
|
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
|
||||||
|
interceptor.setValidatorModules(Collections.singletonList(validatorModule));
|
||||||
|
registerInterceptor(interceptor);
|
||||||
|
}
|
||||||
|
if (HapiProperties.getValidateResponsesEnabled()) {
|
||||||
|
ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor();
|
||||||
|
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
|
||||||
|
interceptor.setValidatorModules(Collections.singletonList(validatorModule));
|
||||||
|
registerInterceptor(interceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphQL
|
||||||
|
if (HapiProperties.getGraphqlEnabled()) {
|
||||||
|
if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
|
||||||
|
registerProvider(appCtx.getBean(GraphQLProvider.class));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HapiProperties.getAllowedBundleTypes().isEmpty()) {
|
||||||
|
String allowedBundleTypesString = HapiProperties.getAllowedBundleTypes();
|
||||||
|
Set<String> allowedBundleTypes = new HashSet<>();
|
||||||
|
Arrays.stream(allowedBundleTypesString.split(",")).forEach(o -> {
|
||||||
|
BundleType type = BundleType.valueOf(o);
|
||||||
|
allowedBundleTypes.add(type.toCode());
|
||||||
|
});
|
||||||
|
DaoConfig config = appCtx.getBean(DaoConfig.class);
|
||||||
|
config.setBundleTypesAllowedForStorage(
|
||||||
|
Collections.unmodifiableSet(new TreeSet<>(allowedBundleTypes)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ cors.allowCredentials=true
|
|||||||
# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca
|
# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca
|
||||||
cors.allow_origin=*
|
cors.allow_origin=*
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
# Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE)
|
||||||
|
##################################################
|
||||||
|
#allowed_bundle_types=COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
# Subscriptions
|
# Subscriptions
|
||||||
##################################################
|
##################################################
|
||||||
|
|||||||
Reference in New Issue
Block a user