Merge branch 'rel_4_0_0'

This commit is contained in:
James Agnew
2019-08-14 11:49:50 -04:00
12 changed files with 451 additions and 72 deletions

View File

@@ -13,6 +13,8 @@ public class ApplicationContext extends AnnotationConfigWebApplicationContext {
register(FhirServerConfigDstu3.class, FhirServerConfigCommon.class);
} else if (fhirVersion == FhirVersionEnum.R4) {
register(FhirServerConfigR4.class, FhirServerConfigCommon.class);
} else if (fhirVersion == FhirVersionEnum.R5) {
register(FhirServerConfigR5.class, FhirServerConfigCommon.class);
} else {
throw new IllegalStateException();
}

View File

@@ -1,15 +1,18 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.jpa.binstore.DatabaseBlobBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
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.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thymeleaf.util.Validate;
@@ -108,6 +111,8 @@ public class FhirServerConfigCommon {
retVal.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET);
}
retVal.setFilterParameterEnabled(HapiProperties.getFilterSearchEnabled());
return retVal;
}
@@ -149,6 +154,12 @@ public class FhirServerConfigCommon {
return retVal;
}
@Lazy
@Bean
public IBinaryStorageSvc binaryStorageSvc() {
return new DatabaseBlobBinaryStorageSvcImpl();
}
@Bean()
public IEmailSender emailSender() {
if (this.emailEnabled) {

View File

@@ -0,0 +1,58 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.jpa.config.BaseJavaConfigR4;
import ca.uhn.fhir.jpa.config.BaseJavaConfigR5;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
public class FhirServerConfigR5 extends BaseJavaConfigR5 {
@Autowired
private DataSource myDataSource;
/**
* We override the paging provider definition so that we can customize
* the default/max page sizes for search results. You can set these however
* you want, although very large page sizes will require a lot of RAM.
*/
@Override
public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize());
pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize());
return pagingProvider;
}
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
}
retVal.setJpaProperties(HapiProperties.getProperties());
return retVal;
}
@Bean()
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
}

View File

@@ -6,11 +6,17 @@ import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import com.google.common.annotations.VisibleForTesting;
import java.io.InputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.*;
public class HapiProperties {
public static final String BINARY_STORAGE_ENABLED = "binary_storage.enabled";
static final String ALLOW_EXTERNAL_REFERENCES = "allow_external_references";
static final String ALLOW_MULTIPLE_DELETE = "allow_multiple_delete";
static final String ALLOW_PLACEHOLDER_REFERENCES = "allow_placeholder_references";
@@ -25,6 +31,7 @@ public class HapiProperties {
static final String DEFAULT_PRETTY_PRINT = "default_pretty_print";
static final String ETAG_SUPPORT = "etag_support";
static final String FHIR_VERSION = "fhir_version";
static final String ALLOW_CASCADING_DELETES = "allow_cascading_deletes";
static final String HAPI_PROPERTIES = "hapi.properties";
static final String LOGGER_ERROR_FORMAT = "logger.error_format";
static final String LOGGER_FORMAT = "logger.format";
@@ -46,7 +53,10 @@ public class HapiProperties {
static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches";
static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params";
static final String EMAIL_FROM = "email.from";
private static final String VALIDATE_REQUESTS_ENABLED = "validation.requests.enabled";
private static final String VALIDATE_RESPONSES_ENABLED = "validation.responses.enabled";
private static final String FILTER_SEARCH_ENABLED = "filter_search.enabled";
private static final String GRAPHQL_ENABLED = "graphql.enabled";
private static Properties properties;
/*
@@ -69,7 +79,7 @@ public class HapiProperties {
public static Properties getProperties() {
if (properties == null) {
// Load the configurable properties file
try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)){
try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)) {
HapiProperties.properties = new Properties();
HapiProperties.properties.load(in);
} catch (Exception e) {
@@ -77,8 +87,8 @@ public class HapiProperties {
}
Properties overrideProps = loadOverrideProperties();
if(overrideProps != null) {
properties.putAll(overrideProps);
if (overrideProps != null) {
properties.putAll(overrideProps);
}
}
@@ -88,17 +98,17 @@ public class HapiProperties {
/**
* If a configuration file path is explicitly specified via -Dhapi.properties=<path>, the properties there will
* be used to override the entries in the default hapi.properties file (currently under WEB-INF/classes)
*
* @return properties loaded from the explicitly specified configuraiton file if there is one, or null otherwise.
*/
private static Properties loadOverrideProperties() {
String confFile = System.getProperty(HAPI_PROPERTIES);
if(confFile != null) {
if (confFile != null) {
try {
Properties props = new Properties();
props.load(new FileInputStream(confFile));
return props;
}
catch (Exception e) {
} catch (Exception e) {
throw new ConfigurationException("Could not load HAPI properties file: " + confFile, e);
}
}
@@ -140,6 +150,10 @@ public class HapiProperties {
return Boolean.parseBoolean(value);
}
private static boolean getBooleanProperty(String propertyName, boolean defaultValue) {
return getBooleanProperty(propertyName, Boolean.valueOf(defaultValue));
}
private static Integer getIntegerProperty(String propertyName, Integer defaultValue) {
String value = HapiProperties.getProperty(propertyName);
@@ -160,6 +174,10 @@ public class HapiProperties {
return FhirVersionEnum.DSTU3;
}
public static boolean isBinaryStorageEnabled() {
return HapiProperties.getBooleanProperty(BINARY_STORAGE_ENABLED, true);
}
public static ETagSupportEnum getEtagSupport() {
String etagSupportString = HapiProperties.getProperty(ETAG_SUPPORT);
@@ -240,6 +258,10 @@ public class HapiProperties {
return HapiProperties.getBooleanProperty(ALLOW_MULTIPLE_DELETE, false);
}
public static Boolean getAllowCascadingDeletes() {
return HapiProperties.getBooleanProperty(ALLOW_CASCADING_DELETES, false);
}
public static Boolean getAllowExternalReferences() {
return HapiProperties.getBooleanProperty(ALLOW_EXTERNAL_REFERENCES, false);
}
@@ -264,6 +286,14 @@ public class HapiProperties {
return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*");
}
public static Set<String> getSupportedResourceTypes() {
String[] types = defaultString(getProperty("supported_resource_types")).split(",");
return Arrays.stream(types)
.map(t -> trim(t))
.filter(t -> isNotBlank(t))
.collect(Collectors.toSet());
}
public static String getServerName() {
return HapiProperties.getProperty(SERVER_NAME, "Local Tester");
}
@@ -328,4 +358,22 @@ public class HapiProperties {
public static Boolean getCorsAllowedCredentials() {
return HapiProperties.getBooleanProperty(CORS_ALLOW_CREDENTIALS, false);
}
public static boolean getValidateRequestsEnabled() {
return HapiProperties.getBooleanProperty(VALIDATE_REQUESTS_ENABLED, false);
}
public static boolean getValidateResponsesEnabled() {
return HapiProperties.getBooleanProperty(VALIDATE_RESPONSES_ENABLED, false);
}
public static boolean getFilterSearchEnabled() {
return HapiProperties.getBooleanProperty(FILTER_SEARCH_ENABLED, true);
}
public static boolean getGraphqlEnabled() {
return HapiProperties.getBooleanProperty(GRAPHQL_ENABLED, true);
}
}

View File

@@ -2,18 +2,20 @@ package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.provider.*;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5;
import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
import ca.uhn.fhir.jpa.subscription.module.interceptor.SubscriptionDebugLogInterceptor;
@@ -22,9 +24,9 @@ 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.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 ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.Meta;
import org.springframework.context.ApplicationContext;
@@ -33,6 +35,8 @@ import org.springframework.web.cors.CorsConfiguration;
import javax.servlet.ServletException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
public class JpaRestfulServer extends RestfulServer {
@@ -48,6 +52,12 @@ public class JpaRestfulServer extends RestfulServer {
* 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
@@ -64,6 +74,9 @@ public class JpaRestfulServer extends RestfulServer {
} 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();
}
@@ -96,6 +109,11 @@ public class JpaRestfulServer extends RestfulServer {
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();
}
@@ -167,11 +185,7 @@ public class JpaRestfulServer extends RestfulServer {
* with this feature.
*/
if (false) { // <-- DISABLED RIGHT NOW
if (fhirVersion == FhirVersionEnum.DSTU3) {
registerProvider(appCtx.getBean(TerminologyUploaderProviderDstu3.class));
} else if (fhirVersion == FhirVersionEnum.R4) {
registerProvider(appCtx.getBean(TerminologyUploaderProviderR4.class));
}
registerProvider(appCtx.getBean(TerminologyUploaderProvider.class));
}
// If you want to enable the $trigger-subscription operation to allow
@@ -226,6 +240,58 @@ public class JpaRestfulServer extends RestfulServer {
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));
}
}
}
}

View File

@@ -22,6 +22,7 @@ allow_override_default_search_params=true
allow_contains_searches=true
allow_multiple_delete=true
allow_external_references=true
allow_cascading_deletes=true
allow_placeholder_references=true
expunge_enabled=true
persistence_unit_name=HAPI_PU
@@ -29,14 +30,40 @@ logger.name=fhirtest.access
logger.format=Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]
logger.error_format=ERROR - ${requestVerb} ${requestUrl}
logger.log_exceptions=true
datasource.driver=org.apache.derby.jdbc.EmbeddedDriver
datasource.url=jdbc:derby:directory:target/jpaserver_derby_files;create=true
datasource.driver=org.h2.Driver
datasource.url=jdbc:h2:file:./target/database/h2
datasource.username=
datasource.password=
server.name=Local Tester
server.id=home
test.port=
hibernate.dialect=ca.uhn.fhir.jpa.util.DerbyTenSevenHapiFhirDialect
###################################################
# Validation
###################################################
# Should all incoming requests be validated
validation.requests.enabled=false
# Should outgoing responses be validated
validation.responses.enabled=false
###################################################
# Search Features
###################################################
filter_search.enabled=true
graphql.enabled=true
###################################################
# Supported Resources
###################################################
# Enable the following property if you want to customize the
# list of resources that is supported by the server (i.e. to
# disable specific resources)
#supported_resource_types=Patient,Observation,Encounter
###################################################
# Database Settings
###################################################
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory
hibernate.format_sql=false
hibernate.show_sql=false
@@ -50,6 +77,15 @@ hibernate.search.default.directory_provider=filesystem
hibernate.search.default.indexBase=target/lucenefiles
hibernate.search.lucene_version=LUCENE_CURRENT
tester.config.refuse_to_fetch_third_party_urls=false
##################################################
# Binary Storage Operations
##################################################
binary_storage.enabled=true
##################################################
# CORS Settings
##################################################
cors.enabled=true
cors.allowCredentials=true
# Supports multiple, comma separated allowed origin entries