diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2421e38 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.exclude": { + "**/.classpath": true, + "**/.project": true, + "**/.settings": true, + "**/.factorypath": true + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index bc49c7e..0e66d1c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,6 +3,8 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + @@ -118,7 +119,7 @@ hapi-fhir-testpage-overlay ${project.version} classes - provided + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + ca.uhn.fhir.jpa.starter.Application + + + + + + org.apache.maven.plugins maven-compiler-plugin + 3.8.1 - 1.8 - 1.8 + 8 @@ -323,7 +357,7 @@ hapi-fhir-testpage-overlay - src/main/webapp/WEB-INF/web.xml + false @@ -438,9 +472,51 @@ - - + + + + + + + + + boot + + + org.springframework.boot + spring-boot-starter-web + 2.3.3.RELEASE + + + + + + + + jetty + + + org.springframework.boot + spring-boot-starter-web + 2.3.3.RELEASE + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + diff --git a/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfig.java b/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfig.java index b1f74a2..66317fc 100644 --- a/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfig.java @@ -1,13 +1,20 @@ package ca.uhn.fhir.jpa.empi; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.empi.api.IEmpiSettings; import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator; import ca.uhn.fhir.empi.rules.config.EmpiSettings; -import ca.uhn.fhir.jpa.starter.HapiProperties; +import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig; +import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig; +import ca.uhn.fhir.jpa.starter.AppProperties; +import ca.uhn.fhir.rest.server.util.ISearchParamRetriever; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; @@ -18,14 +25,21 @@ import java.io.IOException; * in 5.1.0 picks this up even if EMPI is disabled currently. */ @Configuration +@Conditional(EmpiConfigCondition.class) +@Import({EmpiConsumerConfig.class, EmpiSubmitterConfig.class}) public class EmpiConfig { @Bean - IEmpiSettings empiSettings(EmpiRuleValidator theEmpiRuleValidator) throws IOException { + EmpiRuleValidator empiRuleValidator(FhirContext theFhirContext, ISearchParamRetriever theSearchParamRetriever) { + return new EmpiRuleValidator(theFhirContext, theSearchParamRetriever); + } + + @Bean + IEmpiSettings empiSettings(@Autowired EmpiRuleValidator theEmpiRuleValidator, AppProperties appProperties) throws IOException { DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); Resource resource = resourceLoader.getResource("empi-rules.json"); String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); - return new EmpiSettings(theEmpiRuleValidator).setEnabled(HapiProperties.getEmpiEnabled()).setScriptText(json); + return new EmpiSettings(theEmpiRuleValidator).setEnabled(appProperties.getEmpi_enabled()).setScriptText(json); } } diff --git a/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfigCondition.java b/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfigCondition.java new file mode 100644 index 0000000..4854bde --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/empi/EmpiConfigCondition.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.jpa.empi; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class EmpiConfigCondition implements Condition { + @Override + public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { + String property = conditionContext.getEnvironment().getProperty("hapi.fhir.empi_enabled"); + return Boolean.parseBoolean(property); + } +} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java new file mode 100644 index 0000000..2835656 --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -0,0 +1,669 @@ +package ca.uhn.fhir.jpa.starter; + + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.jpa.api.config.DaoConfig.ClientIdStrategyEnum; +import ca.uhn.fhir.rest.api.EncodingEnum; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import org.hl7.fhir.r4.model.Bundle; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + + +@ConfigurationProperties(prefix = "hapi.fhir") +@Configuration +@EnableConfigurationProperties +public class AppProperties { + + private Boolean empi_enabled = false; + private Boolean allow_cascading_deletes = false; + private Boolean allow_contains_searches = true; + private Boolean allow_external_references = false; + private Boolean allow_multiple_delete = false; + private Boolean allow_override_default_search_params = true; + private Boolean allow_placeholder_references = true; + private Boolean auto_create_placeholder_reference_targets = true; + private Boolean enable_index_missing_fields = false; + private Boolean enforce_referential_integrity_on_delete = true; + private Boolean enforce_referential_integrity_on_write = true; + private Boolean etag_support_enabled = true; + private Boolean expunge_enabled = true; + private Boolean fhirpath_interceptor_enabled = false; + private Boolean filter_search_enabled = true; + private Boolean graphql_enabled = false; + private Boolean binary_storage_enabled = false; + private Boolean bulk_export_enabled = false; + private Boolean default_pretty_print = true; + private Integer default_page_size = 20; + private Integer max_binary_size = null; + private Integer max_page_size = Integer.MAX_VALUE; + private Integer defer_indexing_for_codesystems_of_size = 100; + private Long retain_cached_searches_mins = 60L; + private Long reuse_cached_search_results_millis = 60000L; + private String server_address = null; + private EncodingEnum default_encoding = EncodingEnum.JSON; + private FhirVersionEnum fhir_version = FhirVersionEnum.R4; + private ClientIdStrategyEnum client_id_strategy = ClientIdStrategyEnum.ALPHANUMERIC; + private List supported_resource_types = new ArrayList<>(); + private List allowed_bundle_types = null; + + private Validation validation = new Validation(); + private List tester = ImmutableList.of(new Tester()); + private Logger logger = new Logger(); + private Subscription subscription = new Subscription(); + private Cors cors = null; + private Partitioning partitioning = null; + private List implementationGuides = null; + + public Integer getDefer_indexing_for_codesystems_of_size() { + return defer_indexing_for_codesystems_of_size; + } + + public void setDefer_indexing_for_codesystems_of_size(Integer defer_indexing_for_codesystems_of_size) { + this.defer_indexing_for_codesystems_of_size = defer_indexing_for_codesystems_of_size; + } + + public List getImplementationGuides() { + return implementationGuides; + } + + public void setImplementationGuides(List implementationGuides) { + this.implementationGuides = implementationGuides; + } + + public Partitioning getPartitioning() { + return partitioning; + } + + public void setPartitioning(Partitioning partitioning) { + this.partitioning = partitioning; + } + + public Boolean getEmpi_enabled() { + return empi_enabled; + } + + public void setEmpi_enabled(Boolean empi_enabled) { + this.empi_enabled = empi_enabled; + } + + + public Cors getCors() { + return cors; + } + + public void setCors(Cors cors) { + this.cors = cors; + } + + public List getAllowed_bundle_types() { + return allowed_bundle_types; + } + + public void setAllowed_bundle_types(List allowed_bundle_types) { + this.allowed_bundle_types = allowed_bundle_types; + } + + public String getServer_address() { + return server_address; + } + + public void setServer_address(String server_address) { + this.server_address = server_address; + } + + public Subscription getSubscription() { + return subscription; + } + + public Boolean getDefault_pretty_print() { + return default_pretty_print; + } + + public void setDefault_pretty_print(Boolean default_pretty_print) { + this.default_pretty_print = default_pretty_print; + } + + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + + public Validation getValidation() { + return validation; + } + + public void setValidation(Validation validation) { + this.validation = validation; + } + + public List getSupported_resource_types() { + return supported_resource_types; + } + + public void setSupported_resource_types(List supported_resource_types) { + this.supported_resource_types = supported_resource_types; + } + + public Logger getLogger() { + return logger; + } + + public void setLogger(Logger logger) { + this.logger = logger; + } + + public ClientIdStrategyEnum getClient_id_strategy() { + return client_id_strategy; + } + + public void setClient_id_strategy( + ClientIdStrategyEnum client_id_strategy) { + this.client_id_strategy = client_id_strategy; + } + + public Boolean getAllow_cascading_deletes() { + return allow_cascading_deletes; + } + + public void setAllow_cascading_deletes(Boolean allow_cascading_deletes) { + this.allow_cascading_deletes = allow_cascading_deletes; + } + + public Boolean getAllow_contains_searches() { + return allow_contains_searches; + } + + public void setAllow_contains_searches(Boolean allow_contains_searches) { + this.allow_contains_searches = allow_contains_searches; + } + + public Boolean getAllow_external_references() { + return allow_external_references; + } + + public void setAllow_external_references(Boolean allow_external_references) { + this.allow_external_references = allow_external_references; + } + + public Boolean getAllow_multiple_delete() { + return allow_multiple_delete; + } + + public void setAllow_multiple_delete(Boolean allow_multiple_delete) { + this.allow_multiple_delete = allow_multiple_delete; + } + + public Boolean getAllow_override_default_search_params() { + return allow_override_default_search_params; + } + + public void setAllow_override_default_search_params( + Boolean allow_override_default_search_params) { + this.allow_override_default_search_params = allow_override_default_search_params; + } + + public Boolean getAllow_placeholder_references() { + return allow_placeholder_references; + } + + public void setAllow_placeholder_references(Boolean allow_placeholder_references) { + this.allow_placeholder_references = allow_placeholder_references; + } + + public Boolean getAuto_create_placeholder_reference_targets() { + return auto_create_placeholder_reference_targets; + } + + public void setAuto_create_placeholder_reference_targets( + Boolean auto_create_placeholder_reference_targets) { + this.auto_create_placeholder_reference_targets = auto_create_placeholder_reference_targets; + } + + public Integer getDefault_page_size() { + return default_page_size; + } + + public void setDefault_page_size(Integer default_page_size) { + this.default_page_size = default_page_size; + } + + public Boolean getEnable_index_missing_fields() { + return enable_index_missing_fields; + } + + public void setEnable_index_missing_fields(Boolean enable_index_missing_fields) { + this.enable_index_missing_fields = enable_index_missing_fields; + } + + public Boolean getEnforce_referential_integrity_on_delete() { + return enforce_referential_integrity_on_delete; + } + + public void setEnforce_referential_integrity_on_delete( + Boolean enforce_referential_integrity_on_delete) { + this.enforce_referential_integrity_on_delete = enforce_referential_integrity_on_delete; + } + + public Boolean getEnforce_referential_integrity_on_write() { + return enforce_referential_integrity_on_write; + } + + public void setEnforce_referential_integrity_on_write( + Boolean enforce_referential_integrity_on_write) { + this.enforce_referential_integrity_on_write = enforce_referential_integrity_on_write; + } + + public Boolean getEtag_support_enabled() { + return etag_support_enabled; + } + + public void setEtag_support_enabled(Boolean etag_support_enabled) { + this.etag_support_enabled = etag_support_enabled; + } + + public Boolean getExpunge_enabled() { + return expunge_enabled; + } + + public void setExpunge_enabled(Boolean expunge_enabled) { + this.expunge_enabled = expunge_enabled; + } + + public Boolean getFhirpath_interceptor_enabled() { + return fhirpath_interceptor_enabled; + } + + public void setFhirpath_interceptor_enabled(Boolean fhirpath_interceptor_enabled) { + this.fhirpath_interceptor_enabled = fhirpath_interceptor_enabled; + } + + public Boolean getFilter_search_enabled() { + return filter_search_enabled; + } + + public void setFilter_search_enabled(Boolean filter_search_enabled) { + this.filter_search_enabled = filter_search_enabled; + } + + public Boolean getGraphql_enabled() { + return graphql_enabled; + } + + public void setGraphql_enabled(Boolean graphql_enabled) { + this.graphql_enabled = graphql_enabled; + } + + public Boolean getBinary_storage_enabled() { + return binary_storage_enabled; + } + + public void setBinary_storage_enabled(Boolean binary_storage_enabled) { + this.binary_storage_enabled = binary_storage_enabled; + } + + public Boolean getBulk_export_enabled() { + return bulk_export_enabled; + } + + public void setBulk_export_enabled(Boolean bulk_export_enabled) { + this.bulk_export_enabled = bulk_export_enabled; + } + + public EncodingEnum getDefault_encoding() { + return default_encoding; + } + + public void setDefault_encoding(EncodingEnum default_encoding) { + this.default_encoding = default_encoding; + } + + public FhirVersionEnum getFhir_version() { + return fhir_version; + } + + public void setFhir_version(FhirVersionEnum fhir_version) { + this.fhir_version = fhir_version; + } + + public Integer getMax_binary_size() { + return max_binary_size; + } + + public void setMax_binary_size(Integer max_binary_size) { + this.max_binary_size = max_binary_size; + } + + public Integer getMax_page_size() { + return max_page_size; + } + + public void setMax_page_size(Integer max_page_size) { + this.max_page_size = max_page_size; + } + + public Long getRetain_cached_searches_mins() { + return retain_cached_searches_mins; + } + + public void setRetain_cached_searches_mins(Long retain_cached_searches_mins) { + this.retain_cached_searches_mins = retain_cached_searches_mins; + } + + public Long getReuse_cached_search_results_millis() { + return reuse_cached_search_results_millis; + } + + public void setReuse_cached_search_results_millis(Long reuse_cached_search_results_millis) { + this.reuse_cached_search_results_millis = reuse_cached_search_results_millis; + } + + public List getTester() { + return tester; + } + + public void setTester(List tester) { + this.tester = tester; + } + + public static class Cors { + private Boolean allow_Credentials = true; + private List allowed_origin = ImmutableList.of("*"); + + public List getAllowed_origin() { + return allowed_origin; + } + + public void setAllowed_origin(List allowed_origin) { + this.allowed_origin = allowed_origin; + } + + public Boolean getAllow_Credentials() { + return allow_Credentials; + } + + public void setAllow_Credentials(Boolean allow_Credentials) { + this.allow_Credentials = allow_Credentials; + } + + + } + + public static class Logger { + + private String name = "fhirtest.access"; + private String error_format = "ERROR - ${requestVerb} ${requestUrl}"; + private String format = "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"; + private Boolean log_exceptions = true; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getError_format() { + return error_format; + } + + public void setError_format(String error_format) { + this.error_format = error_format; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public Boolean getLog_exceptions() { + return log_exceptions; + } + + public void setLog_exceptions(Boolean log_exceptions) { + this.log_exceptions = log_exceptions; + } + } + + + public static class Tester { + + private String id = "home"; + private String name = "Local Tester"; + private String server_address = "http://localhost:8080/fhir"; + private Boolean refuse_to_fetch_third_party_urls = true; + private FhirVersionEnum fhir_version = FhirVersionEnum.R4; + + public FhirVersionEnum getFhir_version() { + return fhir_version; + } + + public void setFhir_version(FhirVersionEnum fhir_version) { + this.fhir_version = fhir_version; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getServer_address() { + return server_address; + } + + public void setServer_address(String server_address) { + this.server_address = server_address; + } + + public Boolean getRefuse_to_fetch_third_party_urls() { + return refuse_to_fetch_third_party_urls; + } + + public void setRefuse_to_fetch_third_party_urls(Boolean refuse_to_fetch_third_party_urls) { + this.refuse_to_fetch_third_party_urls = refuse_to_fetch_third_party_urls; + } + } + + public static class ImplementationGuide + { + private String url; + private String name; + private String version; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + public static class Validation { + + private Boolean requests_enabled = false; + private Boolean responses_enabled = false; + + public Boolean getRequests_enabled() { + return requests_enabled; + } + + public void setRequests_enabled(Boolean requests_enabled) { + this.requests_enabled = requests_enabled; + } + + public Boolean getResponses_enabled() { + return responses_enabled; + } + + public void setResponses_enabled(Boolean responses_enabled) { + this.responses_enabled = responses_enabled; + } + } + + public static class Partitioning { + + private Boolean partitioning_include_in_search_hashes = false; + + + public Boolean getPartitioning_include_in_search_hashes() { + return partitioning_include_in_search_hashes; + } + + public void setPartitioning_include_in_search_hashes(Boolean partitioning_include_in_search_hashes) { + this.partitioning_include_in_search_hashes = partitioning_include_in_search_hashes; + } + } + + public static class Subscription { + + public Boolean getResthook_enabled() { + return resthook_enabled; + } + + public void setResthook_enabled(Boolean resthook_enabled) { + this.resthook_enabled = resthook_enabled; + } + + public Boolean getWebsocket_enabled() { + return websocket_enabled; + } + + public void setWebsocket_enabled(Boolean websocket_enabled) { + this.websocket_enabled = websocket_enabled; + } + + private Boolean resthook_enabled = false; + private Boolean websocket_enabled = false; + private Email email = null; + + public Email getEmail() { + return email; + } + + public void setEmail(Email email) { + this.email = email; + } + + + public static class Email { + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public Integer getPort() { + return port; + } + + public void setPort(Integer port) { + this.port = port; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Boolean getAuth() { + return auth; + } + + public void setAuth(Boolean auth) { + this.auth = auth; + } + + public Boolean getStartTlsEnable() { + return startTlsEnable; + } + + public void setStartTlsEnable(Boolean startTlsEnable) { + this.startTlsEnable = startTlsEnable; + } + + public Boolean getStartTlsRequired() { + return startTlsRequired; + } + + public void setStartTlsRequired(Boolean startTlsRequired) { + this.startTlsRequired = startTlsRequired; + } + + public Boolean getQuitWait() { + return quitWait; + } + + public void setQuitWait(Boolean quitWait) { + this.quitWait = quitWait; + } + + private String from; + private String host; + private Integer port = 25; + private String username; + private String password; + private Boolean auth = false; + private Boolean startTlsEnable = false; + private Boolean startTlsRequired = false; + private Boolean quitWait = false; + } + } +} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/Application.java b/src/main/java/ca/uhn/fhir/jpa/starter/Application.java new file mode 100644 index 0000000..b0aa670 --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/Application.java @@ -0,0 +1,76 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.jpa.empi.EmpiConfig; +import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; +import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; +import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; +import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.ServletComponentScan; +import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +@ServletComponentScan(basePackageClasses = { + JpaRestfulServer.class}, basePackages = "ca.uhn.fhir.jpa.starter") +@SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class}, scanBasePackages = {"ca.uhn.fhir.jpa.starter"}) +@Import({SubscriptionSubmitterConfig.class, SubscriptionProcessorConfig.class, SubscriptionChannelConfig.class, WebsocketDispatcherConfig.class, EmpiConfig.class}) +public class Application extends SpringBootServletInitializer { + + public static void main(String[] args) { + + System.setProperty("spring.batch.job.enabled", "false"); + SpringApplication.run(Application.class, args); + + //Server is now accessible at eg. http://localhost:8080/hapi-fhir-jpaserver/fhir/metadata + //UI is now accessible at http://localhost:8080/hapi-fhir-jpaserver/ + } + + @Override + protected SpringApplicationBuilder configure( + SpringApplicationBuilder builder) { + return builder.sources(Application.class); + } + + @Autowired + AutowireCapableBeanFactory beanFactory; + + @Bean + public ServletRegistrationBean hapiServletRegistration() { + ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); + JpaRestfulServer jpaRestfulServer = new JpaRestfulServer(); + beanFactory.autowireBean(jpaRestfulServer); + servletRegistrationBean.setServlet(jpaRestfulServer); + servletRegistrationBean.addUrlMappings("/hapi-fhir-jpaserver/fhir/*"); + servletRegistrationBean.setLoadOnStartup(1); + + return servletRegistrationBean; + } + + @Bean + public ServletRegistrationBean overlayRegistrationBean() { + + AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext(); + annotationConfigWebApplicationContext.register(FhirTesterConfig.class); + + DispatcherServlet dispatcherServlet = new DispatcherServlet( + annotationConfigWebApplicationContext); + dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class); + dispatcherServlet.setContextConfigLocation(FhirTesterConfig.class.getName()); + + ServletRegistrationBean registrationBean = new ServletRegistrationBean(); + registrationBean.setServlet(dispatcherServlet); + registrationBean.addUrlMappings("/hapi-fhir-jpaserver/*", "/*"); + registrationBean.setLoadOnStartup(1); + return registrationBean; + + } +} diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java b/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java deleted file mode 100644 index 0beda1d..0000000 --- a/src/main/java/ca/uhn/fhir/jpa/starter/ApplicationContext.java +++ /dev/null @@ -1,49 +0,0 @@ -package ca.uhn.fhir.jpa.starter; - -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.jpa.empi.EmpiConfig; -import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig; -import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig; -import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; -import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; -import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; -import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -public class ApplicationContext extends AnnotationConfigWebApplicationContext { - - public ApplicationContext() { - FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion(); - if (fhirVersion == FhirVersionEnum.DSTU2) { - register(FhirServerConfigDstu2.class, FhirServerConfigCommon.class); - } else if (fhirVersion == FhirVersionEnum.DSTU3) { - 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(); - } - - if (HapiProperties.getSubscriptionWebsocketEnabled()) { - register(WebsocketDispatcherConfig.class); - } - - if (HapiProperties.getSubscriptionEmailEnabled() - || HapiProperties.getSubscriptionRestHookEnabled() - || HapiProperties.getSubscriptionWebsocketEnabled()) { - register(SubscriptionSubmitterConfig.class); - register(SubscriptionProcessorConfig.class); - register(SubscriptionChannelConfig.class); - } - - if (HapiProperties.getEmpiEnabled()) { - register(EmpiSubmitterConfig.class); - register(EmpiConsumerConfig.class); - register(EmpiConfig.class); - } - - } - -} 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 5b482c8..814395f 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java @@ -10,52 +10,95 @@ 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.interceptor.CascadingDeleteInterceptor; +import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; +import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.jpa.provider.GraphQLProvider; +import ca.uhn.fhir.jpa.provider.IJpaSystemProvider; 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.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; -import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; -import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4; 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.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; -import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; +import ca.uhn.fhir.rest.server.ETagSupportEnum; 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.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.*; 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.validation.IValidatorModule; import ca.uhn.fhir.validation.ResultSeverityEnum; -import org.hl7.fhir.dstu3.model.Bundle; -import org.hl7.fhir.dstu3.model.Meta; +import com.google.common.base.Strings; import org.hl7.fhir.r4.model.Bundle.BundleType; -import org.springframework.context.ApplicationContext; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.web.cors.CorsConfiguration; import javax.servlet.ServletException; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.TreeSet; +import java.util.List; +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; + + public BaseJpaRestfulServer() { + + } + private static final long serialVersionUID = 1L; @SuppressWarnings("unchecked") @@ -67,47 +110,19 @@ public class BaseJpaRestfulServer extends RestfulServer { * 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 supportedResourceTypes = HapiProperties.getSupportedResourceTypes(); + List supportedResourceTypes = appProperties.getSupported_resource_types(); if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) { supportedResourceTypes.add("SearchParameter"); - } - - 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)); - + setFhirContext(fhirSystemDao.getContext()); registerProviders(resourceProviders.createProviders()); - registerProvider(systemProvider); + registerProvider(jpaSystemProvider); + FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion(); /* * 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 @@ -116,27 +131,31 @@ public class BaseJpaRestfulServer extends RestfulServer { * You can also create your own subclass of the conformance provider if you need to * provide further customization of your server's CapabilityStatement */ - DaoConfig daoConfig = appCtx.getBean(DaoConfig.class); - ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class); + + if (fhirVersion == FhirVersionEnum.DSTU2) { - IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class); - JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, daoConfig); + + JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, fhirSystemDao, + daoConfig); confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); setServerConformanceProvider(confProvider); } else { if (fhirVersion == FhirVersionEnum.DSTU3) { - IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class); - JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, daoConfig, searchParamRegistry); + + JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, fhirSystemDao, + daoConfig, searchParamRegistry); confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.R4) { - IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoR4", IFhirSystemDao.class); - JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, daoConfig, searchParamRegistry); + + JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, fhirSystemDao, + daoConfig, searchParamRegistry); confProvider.setImplementationDescription("HAPI FHIR R4 Server"); setServerConformanceProvider(confProvider); } else if (fhirVersion == FhirVersionEnum.R5) { - IFhirSystemDao systemDao = appCtx.getBean("mySystemDaoR5", IFhirSystemDao.class); - JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, daoConfig, searchParamRegistry); + + JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, fhirSystemDao, + daoConfig, searchParamRegistry); confProvider.setImplementationDescription("HAPI FHIR R5 Server"); setServerConformanceProvider(confProvider); } else { @@ -147,7 +166,10 @@ public class BaseJpaRestfulServer extends RestfulServer { /* * ETag Support */ - setETagSupport(HapiProperties.getEtagSupport()); + + if (appProperties.getEtag_support_enabled() == false) + setETagSupport(ETagSupportEnum.DISABLED); + /* * This server tries to dynamically generate narratives @@ -158,12 +180,12 @@ public class BaseJpaRestfulServer extends RestfulServer { /* * Default to JSON and pretty printing */ - setDefaultPrettyPrint(HapiProperties.getDefaultPrettyPrint()); + setDefaultPrettyPrint(appProperties.getDefault_pretty_print()); /* * Default encoding */ - setDefaultResponseEncoding(HapiProperties.getDefaultEncoding()); + setDefaultResponseEncoding(appProperties.getDefault_encoding()); /* * This configures the server to page search results to and from @@ -171,7 +193,8 @@ public class BaseJpaRestfulServer extends RestfulServer { * a performance hit when performing searches that return lots of results, * but makes the server much more scalable. */ - setPagingProvider(appCtx.getBean(DatabaseBackedPagingProvider.class)); + + setPagingProvider(databaseBackedPagingProvider); /* * This interceptor formats the output using nice colourful @@ -181,7 +204,7 @@ public class BaseJpaRestfulServer extends RestfulServer { ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor(); this.registerInterceptor(responseHighlighterInterceptor); - if (HapiProperties.isFhirPathFilterInterceptorEnabled()) { + if (appProperties.getFhirpath_interceptor_enabled()) { registerInterceptor(new FhirPathFilterInterceptor()); } @@ -189,10 +212,10 @@ public class BaseJpaRestfulServer extends RestfulServer { * 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()); + 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); /* @@ -201,8 +224,8 @@ public class BaseJpaRestfulServer extends RestfulServer { * 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) { + String serverAddress = appProperties.getServer_address(); + if (!Strings.isNullOrEmpty(serverAddress)) { setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); } @@ -214,21 +237,21 @@ public class BaseJpaRestfulServer extends RestfulServer { * with this feature. */ if (false) { // <-- DISABLED RIGHT NOW - registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); + //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 + /* SubscriptionTriggeringProvider retriggeringProvider = appCtx .getBean(SubscriptionTriggeringProvider.class); - registerProvider(retriggeringProvider); + 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()) { + if (appProperties.getCors() != null) { CorsConfiguration config = new CorsConfiguration(); config.addAllowedHeader(HttpHeaders.ORIGIN); config.addAllowedHeader(HttpHeaders.ACCEPT); @@ -238,17 +261,15 @@ public class BaseJpaRestfulServer extends RestfulServer { 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()); + 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(HapiProperties.getCorsAllowedCredentials()); + config.setAllowCredentials(appProperties.getCors().getAllow_Credentials()); // Create the interceptor and register it CorsInterceptor interceptor = new CorsInterceptor(config); @@ -257,39 +278,35 @@ public class BaseJpaRestfulServer extends RestfulServer { // 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()) { + if (appProperties.getSubscription() != null) { // 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(ctx, daoRegistry, interceptorBroadcaster); + + + if (appProperties.getAllow_cascading_deletes()) { + CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, + daoRegistry, interceptorBroadcaster); getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); } // Binary Storage - if (HapiProperties.isBinaryStorageEnabled()) { - BinaryStorageInterceptor binaryStorageInterceptor = appCtx - .getBean(BinaryStorageInterceptor.class); + if (appProperties.getBinary_storage_enabled()) { getInterceptorService().registerInterceptor(binaryStorageInterceptor); } // Validation - IValidatorModule validatorModule = appCtx.getBean(IValidatorModule.class); + if (validatorModule != null) { - if (HapiProperties.getValidateRequestsEnabled()) { + if (appProperties.getValidation().getRequests_enabled()) { RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); interceptor.setValidatorModules(Collections.singletonList(validatorModule)); registerInterceptor(interceptor); } - if (HapiProperties.getValidateResponsesEnabled()) { + if (appProperties.getValidation().getResponses_enabled()) { ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor(); interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); interceptor.setValidatorModules(Collections.singletonList(validatorModule)); @@ -298,40 +315,46 @@ public class BaseJpaRestfulServer extends RestfulServer { } // GraphQL - if (HapiProperties.getGraphqlEnabled()) { + if (appProperties.getGraphql_enabled()) { if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { - registerProvider(appCtx.getBean(GraphQLProvider.class)); + registerProvider(graphQLProvider.get()); } } - if (!HapiProperties.getAllowedBundleTypes().isEmpty()) { - String allowedBundleTypesString = HapiProperties.getAllowedBundleTypes(); - Set allowedBundleTypes = new HashSet<>(); - Arrays.stream(allowedBundleTypesString.split(",")).forEach(o -> { - BundleType type = BundleType.valueOf(o); - allowedBundleTypes.add(type.toCode()); - }); - DaoConfig config = daoConfig; - config.setBundleTypesAllowedForStorage( - Collections.unmodifiableSet(new TreeSet<>(allowedBundleTypes))); + 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 (HapiProperties.getBulkExportEnabled()) { - registerProvider(appCtx.getBean(BulkDataExportProvider.class)); + if (appProperties.getBulk_export_enabled()) { + registerProvider(bulkDataExportProvider); } // Partitioning - if (HapiProperties.getPartitioningMultitenancyEnabled()) { + if (appProperties.getPartitioning() != null) { registerInterceptor(new RequestTenantPartitionInterceptor()); setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); - registerProviders(appCtx.getBean(PartitionManagementProvider.class)); + registerProviders(partitionManagementProvider); } - if (HapiProperties.getClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY) { + if (appProperties.getClient_id_strategy() == DaoConfig.ClientIdStrategyEnum.ANY) { daoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); - daoConfig.setResourceClientIdStrategy(HapiProperties.getClientIdStrategy()); + daoConfig.setResourceClientIdStrategy(appProperties.getClient_id_strategy()); + } + + if (appProperties.getImplementationGuides() != null) { + List guides = appProperties.getImplementationGuides(); + for (AppProperties.ImplementationGuide guide : guides) { + packageInstallerSvc.install(new PackageInstallationSpec() + .setPackageUrl(guide.getUrl()) + .setName(guide.getName()) + .setVersion(guide.getVersion()) + .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); + } } } + } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java b/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java new file mode 100644 index 0000000..036f2be --- /dev/null +++ b/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java @@ -0,0 +1,92 @@ +package ca.uhn.fhir.jpa.starter; + +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.EnumerablePropertySource; +import org.springframework.core.env.PropertySource; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class EnvironmentHelper { + + public static Properties getHibernateProperties(ConfigurableEnvironment environment) { + Properties properties = new Properties(); + if (environment.getProperty("spring.jpa.properties", String.class) == null) { + properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); + properties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory"); + properties.put("hibernate.format_sql", "false"); + properties.put("hibernate.show_sql", "false"); + properties.put("hibernate.hbm2ddl.auto", "update"); + properties.put("hibernate.jdbc.batch_size", "20"); + properties.put("hibernate.cache.use_query_cache", "false"); + properties.put("hibernate.cache.use_second_level_cache", "false"); + properties.put("hibernate.cache.use_structured_entries", "false"); + properties.put("hibernate.cache.use_minimal_puts", "false"); + properties.put("hibernate.search.default.directory_provider", "filesystem"); + properties.put("hibernate.search.default.indexBase", "target/lucenefiles"); + properties.put("hibernate.search.lucene_version", "LUCENE_CURRENT"); + } else { + Arrays.asList(environment.getProperty("spring.jpa.properties", String.class).split(" ")).stream().forEach(s -> + { + String[] values = s.split("="); + properties.put(values[0], values[1]); + }); + } + return properties; + } + + public static Map getPropertiesStartingWith(ConfigurableEnvironment aEnv, + String aKeyPrefix) { + Map result = new HashMap<>(); + + Map map = getAllProperties(aEnv); + + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + + if (key.startsWith(aKeyPrefix)) { + result.put(key, entry.getValue()); + } + } + + return result; + } + + public static Map getAllProperties(ConfigurableEnvironment aEnv) { + Map result = new HashMap<>(); + aEnv.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps))); + return result; + } + + public static Map getAllProperties(PropertySource aPropSource) { + Map result = new HashMap<>(); + + if (aPropSource instanceof CompositePropertySource) { + CompositePropertySource cps = (CompositePropertySource) aPropSource; + cps.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps))); + return result; + } + + if (aPropSource instanceof EnumerablePropertySource) { + EnumerablePropertySource ps = (EnumerablePropertySource) aPropSource; + Arrays.asList(ps.getPropertyNames()).forEach(key -> result.put(key, ps.getProperty(key))); + return result; + } + + return result; + + } + + private static void addAll(Map aBase, Map aToBeAdded) { + for (Map.Entry entry : aToBeAdded.entrySet()) { + if (aBase.containsKey(entry.getKey())) { + continue; + } + + aBase.put(entry.getKey(), entry.getValue()); + } + } +} 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 123b7a0..cfe9bde 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigCommon.java @@ -8,17 +8,14 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender; -import org.apache.commons.dbcp2.BasicDataSource; +import com.google.common.base.Strings; 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; -import java.lang.reflect.InvocationTargetException; -import java.sql.Driver; +import java.util.Optional; /** * This is the primary configuration file for the example server @@ -29,57 +26,34 @@ public class FhirServerConfigCommon { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class); - private Boolean enableIndexMissingFields = HapiProperties.getEnableIndexMissingFields(); - private Boolean autoCreatePlaceholderReferenceTargets = HapiProperties.getAutoCreatePlaceholderReferenceTargets(); - private Boolean enforceReferentialIntegrityOnWrite = HapiProperties.getEnforceReferentialIntegrityOnWrite(); - private Boolean enforceReferentialIntegrityOnDelete = HapiProperties.getEnforceReferentialIntegrityOnDelete(); - 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 emailAuth = HapiProperties.getEmailAuth(); - private Boolean emailStartTlsEnable = HapiProperties.getEmailStartTlsEnable(); - private Boolean emailStartTlsRequired = HapiProperties.getEmailStartTlsRequired(); - private Boolean emailQuitWait = HapiProperties.getEmailQuitWait(); - @Autowired - private ApplicationContext myAppCtx; - 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(AppProperties appProperties) { + ourLog.info("Server configured to " + (appProperties.getAllow_contains_searches() ? "allow" : "deny") + " contains searches"); + ourLog.info("Server configured to " + (appProperties.getAllow_multiple_delete() ? "allow" : "deny") + " multiple deletes"); + ourLog.info("Server configured to " + (appProperties.getAllow_external_references() ? "allow" : "deny") + " external references"); + ourLog.info("Server configured to " + (appProperties.getExpunge_enabled() ? "enable" : "disable") + " expunges"); + ourLog.info("Server configured to " + (appProperties.getAllow_placeholder_references() ? "allow" : "deny") + " placeholder references"); + ourLog.info("Server configured to " + (appProperties.getAllow_override_default_search_params() ? "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 (appProperties.getSubscription().getEmail() != null) { + AppProperties.Subscription.Email email = appProperties.getSubscription().getEmail(); + ourLog.info("Server is configured to enable email with host '" + email.getHost() + "' and port " + email.getPort()); + ourLog.info("Server will use '" + email.getFrom() + "' 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 (!Strings.isNullOrEmpty(email.getUsername())) { + ourLog.info("Server is configured to use username '" + email.getUsername() + "' for email"); } - if (this.emailPassword != null && this.emailPassword.length() > 0) { + if (!Strings.isNullOrEmpty(email.getPassword())) { ourLog.info("Server is configured to use a password for email"); } } - if (this.subscriptionRestHookEnabled) { + if (appProperties.getSubscription().getResthook_enabled()) { ourLog.info("REST-hook subscriptions enabled"); } - if (this.subscriptionEmailEnabled) { + if (appProperties.getSubscription().getEmail() != null) { ourLog.info("Email subscriptions enabled"); } } @@ -88,56 +62,60 @@ public class FhirServerConfigCommon { * Configure FHIR properties around the the JPA server via this bean */ @Bean() - public DaoConfig daoConfig() { + public DaoConfig daoConfig(AppProperties appProperties) { DaoConfig retVal = new DaoConfig(); - retVal.setIndexMissingFields(this.enableIndexMissingFields ? DaoConfig.IndexEnabledEnum.ENABLED : DaoConfig.IndexEnabledEnum.DISABLED); - retVal.setAutoCreatePlaceholderReferenceTargets(this.autoCreatePlaceholderReferenceTargets); - retVal.setEnforceReferentialIntegrityOnWrite(this.enforceReferentialIntegrityOnWrite); - retVal.setEnforceReferentialIntegrityOnDelete(this.enforceReferentialIntegrityOnDelete); - 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.setIndexMissingFields(appProperties.getEnable_index_missing_fields() ? DaoConfig.IndexEnabledEnum.ENABLED : DaoConfig.IndexEnabledEnum.DISABLED); + retVal.setAutoCreatePlaceholderReferenceTargets(appProperties.getAuto_create_placeholder_reference_targets()); + retVal.setEnforceReferentialIntegrityOnWrite(appProperties.getEnforce_referential_integrity_on_write()); + retVal.setEnforceReferentialIntegrityOnDelete(appProperties.getEnforce_referential_integrity_on_delete()); + retVal.setAllowContainsSearches(appProperties.getAllow_contains_searches()); + retVal.setAllowMultipleDelete(appProperties.getAllow_multiple_delete()); + retVal.setAllowExternalReferences(appProperties.getAllow_external_references()); + retVal.setExpungeEnabled(appProperties.getExpunge_enabled()); + retVal.setAutoCreatePlaceholderReferenceTargets(appProperties.getAllow_placeholder_references()); + if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) + retVal.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom()); - Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); + Integer maxFetchSize = appProperties.getMax_page_size(); retVal.setFetchSizeDefaultMaximum(maxFetchSize); ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); - Long reuseCachedSearchResultsMillis = HapiProperties.getReuseCachedSearchResultsMillis(); + Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis(); retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis); - Long retainCachedSearchesMinutes = HapiProperties.getExpireSearchResultsAfterMins(); + + Long retainCachedSearchesMinutes = appProperties.getRetain_cached_searches_mins(); retVal.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000); - // Subscriptions are enabled by channel type - if (HapiProperties.getSubscriptionRestHookEnabled()) { - ourLog.info("Enabling REST-hook subscriptions"); - retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK); - } - if (HapiProperties.getSubscriptionEmailEnabled()) { - ourLog.info("Enabling email subscriptions"); - retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL); - } - if (HapiProperties.getSubscriptionWebsocketEnabled()) { - ourLog.info("Enabling websocket subscriptions"); - retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET); + if(appProperties.getSubscription() != null) { + // Subscriptions are enabled by channel type + if (appProperties.getSubscription().getResthook_enabled()) { + ourLog.info("Enabling REST-hook subscriptions"); + retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK); + } + if (appProperties.getSubscription().getEmail() != null) { + ourLog.info("Enabling email subscriptions"); + retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL); + } + if (appProperties.getSubscription().getWebsocket_enabled()) { + ourLog.info("Enabling websocket subscriptions"); + retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET); + } } - retVal.setFilterParameterEnabled(HapiProperties.getFilterSearchEnabled()); + retVal.setFilterParameterEnabled(appProperties.getFilter_search_enabled()); return retVal; } @Bean - public PartitionSettings partitionSettings() { + public PartitionSettings partitionSettings(AppProperties appProperties) { PartitionSettings retVal = new PartitionSettings(); // Partitioning - if (HapiProperties.getPartitioningMultitenancyEnabled()) { + if (appProperties.getPartitioning() != null) { retVal.setPartitioningEnabled(true); } @@ -146,19 +124,20 @@ public class FhirServerConfigCommon { @Bean - public ModelConfig modelConfig() { + public ModelConfig modelConfig(AppProperties appProperties) { ModelConfig modelConfig = new ModelConfig(); - modelConfig.setAllowContainsSearches(this.allowContainsSearches); - modelConfig.setAllowExternalReferences(this.allowExternalReferences); - modelConfig.setDefaultSearchParamsCanBeOverridden(this.allowOverrideDefaultSearchParams); - modelConfig.setEmailFromAddress(this.emailFrom); + modelConfig.setAllowContainsSearches(appProperties.getAllow_contains_searches()); + modelConfig.setAllowExternalReferences(appProperties.getAllow_external_references()); + modelConfig.setDefaultSearchParamsCanBeOverridden(appProperties.getAllow_override_default_search_params()); + if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) + modelConfig.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom()); // You can enable these if you want to support Subscriptions from your server - if (this.subscriptionRestHookEnabled) { + if (appProperties.getSubscription() != null && appProperties.getSubscription().getResthook_enabled() != null) { modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); } - if (this.subscriptionEmailEnabled) { + if (appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) { modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); } @@ -171,7 +150,7 @@ public class FhirServerConfigCommon { *

* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. */ - @Bean(destroyMethod = "close") + /*@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(); @@ -181,38 +160,37 @@ public class FhirServerConfigCommon { retVal.setPassword(HapiProperties.getDataSourcePassword()); retVal.setMaxTotal(HapiProperties.getDataSourceMaxPoolSize()); return retVal; - } + }*/ @Lazy @Bean - public IBinaryStorageSvc binaryStorageSvc() { + public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) { DatabaseBlobBinaryStorageSvcImpl binaryStorageSvc = new DatabaseBlobBinaryStorageSvcImpl(); - if (HapiProperties.getMaxBinarySize() != null) { - binaryStorageSvc.setMaximumBinarySize(HapiProperties.getMaxBinarySize()); + if (appProperties.getMax_binary_size() != null) { + binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size()); } return binaryStorageSvc; } @Bean() - public IEmailSender emailSender() { - if (this.emailEnabled) { + public IEmailSender emailSender(AppProperties appProperties, Optional subscriptionDeliveryHandlerFactory) { + if (appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) { JavaMailEmailSender retVal = new JavaMailEmailSender(); - retVal.setSmtpServerHostname(this.emailHost); - retVal.setSmtpServerPort(this.emailPort); - retVal.setSmtpServerUsername(this.emailUsername); - retVal.setSmtpServerPassword(this.emailPassword); - retVal.setAuth(this.emailAuth); - retVal.setStartTlsEnable(this.emailStartTlsEnable); - retVal.setStartTlsRequired(this.emailStartTlsRequired); - retVal.setQuitWait(this.emailQuitWait); - - SubscriptionDeliveryHandlerFactory subscriptionDeliveryHandlerFactory = myAppCtx.getBean(SubscriptionDeliveryHandlerFactory.class); - Validate.notNull(subscriptionDeliveryHandlerFactory, "No subscription delivery handler"); - subscriptionDeliveryHandlerFactory.setEmailSender(retVal); + AppProperties.Subscription.Email email = appProperties.getSubscription().getEmail(); + retVal.setSmtpServerHostname(email.getHost()); + retVal.setSmtpServerPort(email.getPort()); + retVal.setSmtpServerUsername(email.getUsername()); + retVal.setSmtpServerPassword(email.getPassword()); + retVal.setAuth(email.getAuth()); + retVal.setStartTlsEnable(email.getStartTlsEnable()); + retVal.setStartTlsRequired(email.getStartTlsRequired()); + retVal.setQuitWait(email.getQuitWait()); + if(subscriptionDeliveryHandlerFactory.isPresent()) + subscriptionDeliveryHandlerFactory.get().setEmailSender(retVal); return retVal; } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu2.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu2.java index d80e70f..8efc81f 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu2.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu2.java @@ -7,53 +7,66 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; @Configuration +@Profile("dstu2") public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 { - @Autowired - private DataSource myDataSource; + @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.getJpaProperties()); - return retVal; + /** + * 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. + */ + @Autowired + AppProperties appProperties; + + @Override + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider(); + pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size()); + pagingProvider.setMaximumPageSize(appProperties.getMax_page_size()); + return pagingProvider; + } + + @Autowired + private ConfigurableEnvironment configurableEnvironment; + + @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(EnvironmentHelper.getHibernateProperties(configurableEnvironment)); + return retVal; + } @Bean @Primary public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } + } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java index 2a93154..ace88af 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -14,46 +16,53 @@ import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @Configuration +@Profile("dstu3") public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 { - @Autowired - private DataSource myDataSource; + @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; + /** + * 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. + */ + @Autowired + AppProperties appProperties; + + @Override + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider(); + pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size()); + pagingProvider.setMaximumPageSize(appProperties.getMax_page_size()); + return pagingProvider; + } + + @Autowired + private ConfigurableEnvironment configurableEnvironment; + + @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); } - @Override - @Bean - public LocalContainerEntityManagerFactoryBean entityManagerFactory() { - LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); - retVal.setPersistenceUnitName("HAPI_PU"); + retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment)); + return retVal; + } - 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.getJpaProperties()); - return retVal; - } - - @Bean - @Primary - public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } + @Bean + @Primary + public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java index 5f3f8a9..8d8e699 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java @@ -7,53 +7,65 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.Environment; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; +import java.util.Arrays; +import java.util.Properties; @Configuration +@Profile("r4") public class FhirServerConfigR4 extends BaseJavaConfigR4 { - @Autowired - private DataSource myDataSource; + @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; + /** + * 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. + */ + @Autowired + AppProperties appProperties; + + @Override + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider(); + pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size()); + pagingProvider.setMaximumPageSize(appProperties.getMax_page_size()); + return pagingProvider; + } + + @Autowired + private ConfigurableEnvironment configurableEnvironment; + + @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); } - @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.getJpaProperties()); - return retVal; - } + retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment)); + return retVal; + } @Bean @Primary public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java index d6cf537..5a5145b 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java @@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; @@ -14,46 +16,53 @@ import javax.persistence.EntityManagerFactory; import javax.sql.DataSource; @Configuration +@Profile("r5") public class FhirServerConfigR5 extends BaseJavaConfigR5 { - @Autowired - private DataSource myDataSource; + @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; + /** + * 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. + */ + @Autowired + AppProperties appProperties; + + @Override + public DatabaseBackedPagingProvider databaseBackedPagingProvider() { + DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider(); + pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size()); + pagingProvider.setMaximumPageSize(appProperties.getMax_page_size()); + return pagingProvider; + } + + @Autowired + private ConfigurableEnvironment configurableEnvironment; + + @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); } - @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.getJpaProperties()); - return retVal; - } + retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment)); + return retVal; + } @Bean @Primary public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { - JpaTransactionManager retVal = new JpaTransactionManager(); - retVal.setEntityManagerFactory(entityManagerFactory); - return retVal; - } + JpaTransactionManager retVal = new JpaTransactionManager(); + retVal.setEntityManagerFactory(entityManagerFactory); + return retVal; + } } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java b/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java index 11ccbcb..45a7808 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java @@ -1,11 +1,10 @@ package ca.uhn.fhir.jpa.starter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; - import ca.uhn.fhir.to.FhirTesterMvcConfig; import ca.uhn.fhir.to.TesterConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; //@formatter:off /** @@ -34,18 +33,22 @@ public class FhirTesterConfig { * deploying your server to a place with a fully qualified domain name, * you might want to use that instead of using the variable. */ - @Bean - public TesterConfig testerConfig() { - TesterConfig retVal = new TesterConfig(); - retVal - .addServer() - .withId(HapiProperties.getServerId()) - .withFhirVersion(HapiProperties.getFhirVersion()) - .withBaseUrl(HapiProperties.getServerAddress()) - .withName(HapiProperties.getServerName()); - retVal.setRefuseToFetchThirdPartyUrls(HapiProperties.getTesterConfigRefustToFetchThirdPartyUrls()); - return retVal; - } + @Bean + public TesterConfig testerConfig(AppProperties appProperties) { + TesterConfig retVal = new TesterConfig(); + appProperties.getTester().stream().forEach(t -> { + retVal + .addServer() + .withId(t.getId()) + .withFhirVersion(t.getFhir_version()) + .withBaseUrl(t.getServer_address()) + .withName(t.getName()); + retVal.setRefuseToFetchThirdPartyUrls( + t.getRefuse_to_fetch_third_party_urls()); + + }); + return retVal; + } } //@formatter:on diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java deleted file mode 100644 index 20628e5..0000000 --- a/src/main/java/ca/uhn/fhir/jpa/starter/HapiProperties.java +++ /dev/null @@ -1,535 +0,0 @@ -package ca.uhn.fhir.jpa.starter; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import com.google.common.annotations.VisibleForTesting; -import org.apache.commons.lang3.StringUtils; -import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus; -import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy; -import org.jetbrains.annotations.NotNull; - -import javax.annotation.Nonnull; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.Arrays; -import java.util.Locale; -import java.util.Properties; -import java.util.Set; -import java.util.Map; -import java.util.stream.Collectors; - -import static org.apache.commons.lang3.StringUtils.defaultString; -import static org.apache.commons.lang3.StringUtils.isNotBlank; -import static org.apache.commons.lang3.StringUtils.trim; - -public class HapiProperties { - static final String ENABLE_INDEX_MISSING_FIELDS = "enable_index_missing_fields"; - static final String AUTO_CREATE_PLACEHOLDER_REFERENCE_TARGETS = "auto_create_placeholder_reference_targets"; - static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE = "enforce_referential_integrity_on_write"; - static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE = "enforce_referential_integrity_on_delete"; - 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"; - static final String REUSE_CACHED_SEARCH_RESULTS_MILLIS = "reuse_cached_search_results_millis"; - static final String DATASOURCE_DRIVER = "datasource.driver"; - static final String DATASOURCE_MAX_POOL_SIZE = "datasource.max_pool_size"; - static final String DATASOURCE_PASSWORD = "datasource.password"; - static final String DATASOURCE_URL = "datasource.url"; - static final String DATASOURCE_USERNAME = "datasource.username"; - static final String DEFAULT_ENCODING = "default_encoding"; - static final String DEFAULT_PAGE_SIZE = "default_page_size"; - 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"; - static final String LOGGER_LOG_EXCEPTIONS = "logger.log_exceptions"; - static final String LOGGER_NAME = "logger.name"; - static final String MAX_FETCH_SIZE = "max_fetch_size"; - static final String MAX_PAGE_SIZE = "max_page_size"; - static final String SERVER_ADDRESS = "server_address"; - static final String SERVER_ID = "server.id"; - static final String SERVER_NAME = "server.name"; - static final String SUBSCRIPTION_EMAIL_ENABLED = "subscription.email.enabled"; - static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled"; - static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled"; - static final String EMPI_ENABLED = "empi.enabled"; - static final String PARTITIONING_ENABLED = "partitioning.enabled"; - static final String PARTITIONING_CROSS_PARTITION_REFERENCE_MODE = "partitioning.cross_partition_reference_mode"; - static final String ALLOWED_BUNDLE_TYPES = "allowed_bundle_types"; - 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 CORS_ENABLED = "cors.enabled"; - static final String CORS_ALLOWED_ORIGIN = "cors.allowed_origin"; - static final String CORS_ALLOW_CREDENTIALS = "cors.allowCredentials"; - 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"; - static final String VALIDATE_REQUESTS_ENABLED = "validation.requests.enabled"; - static final String VALIDATE_RESPONSES_ENABLED = "validation.responses.enabled"; - static final String FILTER_SEARCH_ENABLED = "filter_search.enabled"; - static final String GRAPHQL_ENABLED = "graphql.enabled"; - static final String BULK_EXPORT_ENABLED = "bulk.export.enabled"; - static final String EXPIRE_SEARCH_RESULTS_AFTER_MINS = "retain_cached_searches_mins"; - static final String MAX_BINARY_SIZE = "max_binary_size"; - static final String PARTITIONING_MULTITENANCY_ENABLED = "partitioning.multitenancy.enabled"; - private static final String PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES = "partitioning.partitioning_include_in_search_hashes"; - static final String CLIENT_ID_STRATEGY = "daoconfig.client_id_strategy"; - private static Properties ourProperties; - - public static boolean isElasticSearchEnabled() { - return HapiProperties.getPropertyBoolean("elasticsearch.enabled", false); - } - - /* - * Force the configuration to be reloaded - */ - public static void forceReload() { - ourProperties = null; - getProperties(); - } - - /** - * This is mostly here for unit tests. Use the actual properties file - * to set values - */ - @VisibleForTesting - public static void setProperty(String theKey, String theValue) { - getProperties().setProperty(theKey, theValue); - } - - public static Properties getJpaProperties() { - Properties retVal = loadProperties(); - - if (isElasticSearchEnabled()) { - ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder(); - builder.setRequiredIndexStatus(getPropertyEnum("elasticsearch.required_index_status", ElasticsearchIndexStatus.class, ElasticsearchIndexStatus.YELLOW)); - builder.setRestUrl(getProperty("elasticsearch.rest_url")); - builder.setUsername(getProperty("elasticsearch.username")); - builder.setPassword(getProperty("elasticsearch.password")); - builder.setIndexSchemaManagementStrategy(getPropertyEnum("elasticsearch.schema_management_strategy", IndexSchemaManagementStrategy.class, IndexSchemaManagementStrategy.CREATE)); - builder.setDebugRefreshAfterWrite(getPropertyBoolean("elasticsearch.debug.refresh_after_write", false)); - builder.setDebugPrettyPrintJsonLog(getPropertyBoolean("elasticsearch.debug.pretty_print_json_log", false)); - builder.apply(retVal); - } - - return retVal; - } - - private static Properties getProperties() { - if (ourProperties == null) { - Properties properties = loadProperties(); - HapiProperties.ourProperties = properties; - } - - return ourProperties; - } - - @NotNull - private static Properties loadProperties() { - // Load the configurable properties file - Properties properties; - try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)) { - properties = new Properties(); - properties.load(in); - } catch (Exception e) { - throw new ConfigurationException("Could not load HAPI properties", e); - } - - Properties overrideProps = loadOverrideProperties(); - if (overrideProps != null) { - properties.putAll(overrideProps); - } - properties.putAll(System.getenv().entrySet() - .stream() - .filter(e -> e.getValue() != null && properties.containsKey(e.getKey())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); - return properties; - } - - /** - * If a configuration file path is explicitly specified via -Dhapi.properties=, 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) { - try { - Properties props = new Properties(); - props.load(new FileInputStream(confFile)); - return props; - } catch (Exception e) { - throw new ConfigurationException("Could not load HAPI properties file: " + confFile, e); - } - } - - return null; - } - - private static String getProperty(String propertyName) { - String env = "HAPI_" + propertyName.toUpperCase(Locale.US); - env = env.replace(".", "_"); - env = env.replace("-", "_"); - - String propertyValue = System.getenv(env); - if (propertyValue != null) { - return propertyValue; - } - - Properties properties = HapiProperties.getProperties(); - if (properties != null) { - propertyValue = properties.getProperty(propertyName); - } - - return propertyValue; - } - - private static String getProperty(String propertyName, String defaultValue) { - String value = getProperty(propertyName); - - if (value != null && value.length() > 0) { - return value; - } - - return defaultValue; - } - - private static Boolean getBooleanProperty(String propertyName, Boolean defaultValue) { - String value = HapiProperties.getProperty(propertyName); - - if (value == null || value.length() == 0) { - return defaultValue; - } - - 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); - - if (value == null || value.length() == 0) { - return defaultValue; - } - - return Integer.parseInt(value); - } - - public static FhirVersionEnum getFhirVersion() { - String fhirVersionString = HapiProperties.getProperty(FHIR_VERSION); - - if (fhirVersionString != null && fhirVersionString.length() > 0) { - return FhirVersionEnum.valueOf(fhirVersionString); - } - - return FhirVersionEnum.DSTU3; - } - - public static boolean isBinaryStorageEnabled() { - return HapiProperties.getBooleanProperty(BINARY_STORAGE_ENABLED, true); - } - - public static ETagSupportEnum getEtagSupport() { - String etagSupportString = HapiProperties.getProperty(ETAG_SUPPORT); - - if (etagSupportString != null && etagSupportString.length() > 0) { - return ETagSupportEnum.valueOf(etagSupportString); - } - - return ETagSupportEnum.ENABLED; - } - - public static DaoConfig.ClientIdStrategyEnum getClientIdStrategy() { - String idStrategy = HapiProperties.getProperty(CLIENT_ID_STRATEGY); - - if (idStrategy != null && idStrategy.length() > 0) { - return DaoConfig.ClientIdStrategyEnum.valueOf(idStrategy); - } - - return DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC; - } - - public static EncodingEnum getDefaultEncoding() { - String defaultEncodingString = HapiProperties.getProperty(DEFAULT_ENCODING); - - if (defaultEncodingString != null && defaultEncodingString.length() > 0) { - return EncodingEnum.valueOf(defaultEncodingString); - } - - return EncodingEnum.JSON; - } - - public static Boolean getDefaultPrettyPrint() { - return HapiProperties.getBooleanProperty(DEFAULT_PRETTY_PRINT, true); - } - - public static String getServerAddress() { - return HapiProperties.getProperty(SERVER_ADDRESS); - } - - public static Integer getDefaultPageSize() { - return HapiProperties.getIntegerProperty(DEFAULT_PAGE_SIZE, 20); - } - - public static Integer getMaximumPageSize() { - return HapiProperties.getIntegerProperty(MAX_PAGE_SIZE, 200); - } - - public static Integer getMaximumFetchSize() { - return HapiProperties.getIntegerProperty(MAX_FETCH_SIZE, Integer.MAX_VALUE); - } - - public static String getLoggerName() { - return HapiProperties.getProperty(LOGGER_NAME, "fhirtest.access"); - } - - public static String getLoggerFormat() { - return HapiProperties.getProperty(LOGGER_FORMAT, "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]"); - } - - public static String getLoggerErrorFormat() { - return HapiProperties.getProperty(LOGGER_ERROR_FORMAT, "ERROR - ${requestVerb} ${requestUrl}"); - } - - public static Boolean getLoggerLogExceptions() { - return HapiProperties.getBooleanProperty(LOGGER_LOG_EXCEPTIONS, true); - } - - public static String getDataSourceDriver() { - return HapiProperties.getProperty(DATASOURCE_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver"); - } - - public static Integer getDataSourceMaxPoolSize() { - return HapiProperties.getIntegerProperty(DATASOURCE_MAX_POOL_SIZE, 10); - } - - public static String getDataSourceUrl() { - return HapiProperties.getProperty(DATASOURCE_URL, "jdbc:derby:directory:target/jpaserver_derby_files;create=true"); - } - - public static String getDataSourceUsername() { - return HapiProperties.getProperty(DATASOURCE_USERNAME); - } - - public static String getDataSourcePassword() { - return HapiProperties.getProperty(DATASOURCE_PASSWORD); - } - - public static Boolean getAllowMultipleDelete() { - 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); - } - - public static Boolean getExpungeEnabled() { - return HapiProperties.getBooleanProperty("expunge_enabled", true); - } - - public static Boolean getTesterConfigRefustToFetchThirdPartyUrls() { - return HapiProperties.getBooleanProperty(TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS, false); - } - - public static Boolean getCorsEnabled() { - return HapiProperties.getBooleanProperty(CORS_ENABLED, true); - } - - public static String getCorsAllowedOrigin() { - return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*"); - } - - public static String getAllowedBundleTypes() { - return HapiProperties.getProperty(ALLOWED_BUNDLE_TYPES, ""); - } - - @Nonnull - public static Set getSupportedResourceTypes() { - String[] types = defaultString(getProperty("supported_resource_types")).split(","); - return Arrays.stream(types) - .map(StringUtils::trim) - .filter(StringUtils::isNotBlank) - .collect(Collectors.toSet()); - } - - public static String getServerName() { - return HapiProperties.getProperty(SERVER_NAME, "Local Tester"); - } - - public static String getServerId() { - return HapiProperties.getProperty(SERVER_ID, "home"); - } - - public static Boolean getAllowPlaceholderReferences() { - return HapiProperties.getBooleanProperty(ALLOW_PLACEHOLDER_REFERENCES, true); - } - - public static Boolean getSubscriptionEmailEnabled() { - return HapiProperties.getBooleanProperty(SUBSCRIPTION_EMAIL_ENABLED, false); - } - - public static Boolean getSubscriptionRestHookEnabled() { - return HapiProperties.getBooleanProperty(SUBSCRIPTION_RESTHOOK_ENABLED, false); - } - - public static Boolean getSubscriptionWebsocketEnabled() { - return HapiProperties.getBooleanProperty(SUBSCRIPTION_WEBSOCKET_ENABLED, false); - } - - public static Boolean getEmpiEnabled() { - return HapiProperties.getBooleanProperty(EMPI_ENABLED, false); - } - - public static Boolean getPartitioningEnabled() { - return HapiProperties.getBooleanProperty(PARTITIONING_ENABLED, false); - } - - - public static String getPartitioningCrossPartitionReferenceMode() { - return HapiProperties.getProperty(PARTITIONING_CROSS_PARTITION_REFERENCE_MODE, "NOT_ALLOWED"); - } - - public static Boolean getIncludePartitionInSearchHashes() { - return HapiProperties.getBooleanProperty(PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES, true); - } - - public static Boolean getAllowContainsSearches() { - return HapiProperties.getBooleanProperty(ALLOW_CONTAINS_SEARCHES, true); - } - - public static Boolean getAllowOverrideDefaultSearchParams() { - return HapiProperties.getBooleanProperty(ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS, true); - } - - public static String getEmailFrom() { - return HapiProperties.getProperty(EMAIL_FROM, "some@test.com"); - } - - public static Boolean getEmailEnabled() { - return HapiProperties.getBooleanProperty("email.enabled", false); - } - - public static String getEmailHost() { - return HapiProperties.getProperty("email.host"); - } - - public static Integer getEmailPort() { - return HapiProperties.getIntegerProperty("email.port", 0); - } - - public static String getEmailUsername() { - return HapiProperties.getProperty("email.username"); - } - - public static String getEmailPassword() { - return HapiProperties.getProperty("email.password"); - } - - // Defaults from https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html - public static Boolean getEmailAuth() { - return HapiProperties.getBooleanProperty("email.auth", false); - } - - public static Boolean getEmailStartTlsEnable() { - return HapiProperties.getBooleanProperty("email.starttls.enable", false); - } - - public static Boolean getEmailStartTlsRequired() { - return HapiProperties.getBooleanProperty("email.starttls.required", false); - } - - public static Boolean getEmailQuitWait() { - return HapiProperties.getBooleanProperty("email.quitwait", true); - } - - public static Long getReuseCachedSearchResultsMillis() { - String value = HapiProperties.getProperty(REUSE_CACHED_SEARCH_RESULTS_MILLIS, "60000"); - return Long.valueOf(value); - } - - public static Long getExpireSearchResultsAfterMins() { - String value = HapiProperties.getProperty(EXPIRE_SEARCH_RESULTS_AFTER_MINS, "60"); - return Long.valueOf(value); - } - - 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); - } - - public static boolean getEnforceReferentialIntegrityOnDelete() { - return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE, true); - } - - public static boolean getEnforceReferentialIntegrityOnWrite() { - return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE, true); - } - - public static boolean getAutoCreatePlaceholderReferenceTargets() { - return HapiProperties.getBooleanProperty(AUTO_CREATE_PLACEHOLDER_REFERENCE_TARGETS, true); - } - - public static boolean getEnableIndexMissingFields() { - return HapiProperties.getBooleanProperty(ENABLE_INDEX_MISSING_FIELDS, false); - } - - public static Integer getMaxBinarySize() { - return getIntegerProperty(MAX_BINARY_SIZE, null); - } - - private static boolean getPropertyBoolean(String thePropertyName, boolean theDefaultValue) { - String value = getProperty(thePropertyName, Boolean.toString(theDefaultValue)); - return Boolean.parseBoolean(value); - } - - private static T getPropertyEnum(String thePropertyName, Class theEnumType, T theDefaultValue) { - String value = getProperty(thePropertyName, theDefaultValue.name()); - return (T) Enum.valueOf(theEnumType, value); - } - - public static boolean getBulkExportEnabled() { - return HapiProperties.getBooleanProperty(BULK_EXPORT_ENABLED, true); - } - - public static boolean isFhirPathFilterInterceptorEnabled() { - return HapiProperties.getBooleanProperty("fhirpath_interceptor.enabled", false); - } - - public static boolean getPartitioningMultitenancyEnabled() { - return HapiProperties.getBooleanProperty(PARTITIONING_MULTITENANCY_ENABLED, false); - } - - -} - 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 f13ab64..c895c22 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/JpaRestfulServer.java @@ -1,11 +1,22 @@ package ca.uhn.fhir.jpa.starter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; + import javax.servlet.ServletException; +@Import(AppProperties.class) public class JpaRestfulServer extends BaseJpaRestfulServer { + @Autowired + AppProperties appProperties; + private static final long serialVersionUID = 1L; + public JpaRestfulServer() { + super(); + } + @Override protected void initialize() throws ServletException { super.initialize(); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml new file mode 100644 index 0000000..7a91683 --- /dev/null +++ b/src/main/resources/application.yaml @@ -0,0 +1,129 @@ +spring: + datasource: + url: 'jdbc:h2:file:./target/database/h2' + username: sa + password: null + driverClassName: org.h2.Driver + max-active: 15 + profiles: + ### This is the FHIR version. Choose between, dstu2, dstu3, r4 or r5 + active: r4 + jpa: + properties: + hibernate.dialect: org.hibernate.dialect.H2Dialect + hibernate.search.model_mapping: ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory + hibernate.format_sql: false + hibernate.show_sql: false + hibernate.hbm2ddl.auto: update + hibernate.jdbc.batch_size: 20 + hibernate.cache.use_query_cache: false + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_structured_entries: false + hibernate.cache.use_minimal_puts: false + hibernate.search.default.directory_provider: filesystem + hibernate.search.default.indexBase: target/lucenefiles + hibernate.search.lucene_version: LUCENE_CURRENT + +hapi: + fhir: + defer_indexing_for_codesystems_of_size: 101 +# implementationguides: +# - +# url: https://build.fhir.org/ig/hl7dk/dk-medcom/branches/corrections/package.tgz +# name: dk.fhir.ig.medcom-core +# version: 0.8.0 +# - +# name: hl7.fhir.uv.ips +# version: 0.3.0 + + #supported_resource_types: + # - Patient + # - Observation +# allow_cascading_deletes: true +# allow_contains_searches: true +# allow_external_references: true +# allow_multiple_delete: true +# allow_override_default_search_params: true +# allow_placeholder_references: true +# auto_create_placeholder_reference_targets: false +# default_encoding: JSON +# default_pretty_print: true +# default_page_size: 20 +# enable_index_missing_fields: false +# enforce_referential_integrity_on_delete: false +# enforce_referential_integrity_on_write: false +# etag_support_enabled: true +# expunge_enabled: true +# daoconfig_client_id_strategy: null +# fhirpath_interceptor_enabled: false +# filter_search_enabled: true +# graphql_enabled: true + #partitioning: + # cross_partition_reference_mode: true + # multitenancy_enabled: true + # partitioning_include_in_search_hashes: true + #cors: + # allow_Credentials: true + # Supports multiple, comma separated allowed origin entries + # cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca + # allowed_origin: + # - '*' + +# logger: +# error_format: 'ERROR - ${requestVerb} ${requestUrl}' +# format: >- +# Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] +# Operation[${operationType} ${operationName} ${idOrResourceName}] +# UA[${requestHeader.user-agent}] Params[${requestParameters}] +# ResponseEncoding[${responseEncodingNoDefault}] +# log_exceptions: true +# name: fhirtest.access +# max_binary_size: 104857600 +# max_page_size: 200 +# retain_cached_searches_mins: 60 +# reuse_cached_search_results_millis: 60000 + tester: + - + id: home + name: Local Tester + server_address: 'http://localhost:8080/hapi-fhir-jpaserver/fhir' + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + - + id: global + name: Global Tester + server_address: "http://hapi.fhir.org/baseR4" + refuse_to_fetch_third_party_urls: false + fhir_version: R4 +# validation: +# requests_enabled: true +# responses_enabled: true +# binary_storage_enabled: true +# bulk_export_enabled: true +# partitioning_multitenancy_enabled: +# subscription: +# resthook_enabled: false +# websocket_enabled: false +# email: +# from: some@test.com +# host: google.com +# port: +# username: +# password: +# auth: +# startTlsEnable: +# startTlsRequired: +# quitWait: + + +# +#elasticsearch: +# debug: +# pretty_print_json_log: false +# refresh_after_write: false +# enabled: false +# password: SomePassword +# required_index_status: YELLOW +# rest_url: 'http://localhost:9200' +# schema_management_strategy: CREATE +# username: SomeUsername diff --git a/src/main/resources/hapi.properties b/src/main/resources/hapi.properties deleted file mode 100644 index b7ef0f8..0000000 --- a/src/main/resources/hapi.properties +++ /dev/null @@ -1,167 +0,0 @@ -# Adjust this to set the version of FHIR supported by this server. See -# FhirVersionEnum for a list of available constants. Example values include -# DSTU2, DSTU3, R4. -fhir_version=R4 - -# This is the address that the FHIR server will report as its own address. -# If this server will be deployed (for example) to an internet accessible -# server, put the DNS name of that server here. -# -# Note that this is also the address that the hapi-fhir-testpage-overlay -# (the web UI similar to the one at http://hapi.fhir.org) will use to -# connect internally to the FHIR server, so this also needs to be a name -# accessible from the server itself. -server_address=http://localhost:8080/hapi-fhir-jpaserver/fhir/ - -enable_index_missing_fields=false -auto_create_placeholder_reference_targets=false -enforce_referential_integrity_on_write=false -enforce_referential_integrity_on_delete=false -default_encoding=JSON -etag_support=ENABLED -reuse_cached_search_results_millis=60000 -retain_cached_searches_mins=60 -default_page_size=20 -max_page_size=200 -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 -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.h2.Driver -datasource.url=jdbc:h2:file:./target/database/h2 -datasource.username= -datasource.password= -server.name=Local Tester -server.id=home -test.port= - -################################################### -# Binary Storage (104857600 = 100mb) -################################################### -max_binary_size=104857600 - -################################################### -# 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 -# See FhirPathFilterInterceptor -fhirpath_interceptor.enabled=false - -################################################### -# 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 -hibernate.hbm2ddl.auto=update -hibernate.jdbc.batch_size=20 -hibernate.cache.use_query_cache=false -hibernate.cache.use_second_level_cache=false -hibernate.cache.use_structured_entries=false -hibernate.cache.use_minimal_puts=false -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 - -################################################## -# ElasticSearch -# Note that using ElasticSearch is disabled by -# default and the server will use Lucene instead. -################################################## -elasticsearch.enabled=false -elasticsearch.rest_url=http://localhost:9200 -elasticsearch.username=SomeUsername -elasticsearch.password=SomePassword -elasticsearch.required_index_status=YELLOW -elasticsearch.schema_management_strategy=CREATE -# Immediately refresh indexes after every write. This is very bad for -# performance, but can be helpful for testing. -elasticsearch.debug.refresh_after_write=false -elasticsearch.debug.pretty_print_json_log=false - - -################################################## -# Binary Storage Operations -################################################## -binary_storage.enabled=true - -################################################## -# Bulk Data Specification -################################################## -bulk.export.enabled=true - -################################################## -# CORS Settings -################################################## -cors.enabled=true -cors.allowCredentials=true -# Supports multiple, comma separated allowed origin entries -# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca -cors.allowed_origin=* - -################################################## -# Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE) -################################################## -#allowed_bundle_types=COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET - -################################################## -# Subscriptions -################################################## - -# Enable REST Hook Subscription Channel -subscription.resthook.enabled=false - -# Enable Email Subscription Channel -subscription.email.enabled=false -email.enabled=false -email.from=some@test.com -email.host= -email.port=0 -email.username= -email.password= - -# Enable Websocket Subscription Channel -subscription.websocket.enabled=false - -################################################### -# EMPI -################################################### -empi.enabled=false - -################################################### -# Partitioning And Multitenancy -################################################### -partitioning.enabled=false -partitioning.cross_partition_reference_mode=NOT_ALLOWED -partitioning.partitioning_include_in_search_hashes=true -partitioning.multitenancy.enabled=false - -#daoconfig.client_id_strategy=ANY - diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 9364495..8039731 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -9,24 +9,6 @@ - - DEBUG - ${smile.basedir}/log/empi-troubleshooting.log - - ${smile.basedir}/log/empi-troubleshooting.log.%i.gz - 1 - 9 - - - 5MB - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n${log.stackfilter.pattern} - - - - - diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 6ce88b0..0000000 --- a/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - org.springframework.web.context.ContextLoaderListener - - - contextClass - - ca.uhn.fhir.jpa.starter.ApplicationContext - - - - contextConfigLocation - - - - - - - spring - org.springframework.web.servlet.DispatcherServlet - - contextClass - org.springframework.web.context.support.AnnotationConfigWebApplicationContext - - - contextConfigLocation - - ca.uhn.fhir.jpa.starter.FhirTesterConfig - - - 2 - - - spring - / - - - - fhirServlet - ca.uhn.fhir.jpa.starter.JpaRestfulServer - 1 - - - fhirServlet - /fhir/* - - - diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/Demo.java b/src/test/java/ca/uhn/fhir/jpa/starter/Demo.java new file mode 100644 index 0000000..5dadf14 --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/Demo.java @@ -0,0 +1,20 @@ +package ca.uhn.fhir.jpa.starter; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; +import org.springframework.boot.web.servlet.ServletComponentScan; + +@ServletComponentScan(basePackageClasses = {JpaRestfulServer.class}) +@SpringBootApplication(exclude = ElasticsearchRestClientAutoConfiguration.class) +public class Demo { + + public static void main(String[] args) { + + System.setProperty("spring.profiles.active", "r4"); + System.setProperty("spring.batch.job.enabled", "false"); + SpringApplication.run(Demo.class, args); + + //Server is now accessible at eg. http://localhost:8080/metadata + } +} diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java index b34e67d..aeab8d7 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java @@ -5,79 +5,53 @@ import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import java.nio.file.Paths; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = + { + "spring.batch.job.enabled=false", + "spring.profiles.active=dstu2", + "spring.datasource.url=jdbc:h2:mem:dbr2" + }) public class ExampleServerDstu2IT { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + private IGenericClient ourClient; + private FhirContext ourCtx; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU2"); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr2"); - ourCtx = FhirContext.forDstu2(); - } + @LocalServerPort + private int port; + + @Test + void testCreateAndRead() { - @Test - public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); String methodName = "testCreateResourceConditional"; Patient pt = new Patient(); pt.addName().addFamily(methodName); IIdType id = ourClient.create().resource(pt).execute().getId(); - Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); assertEquals(methodName, pt2.getName().get(0).getFamily().get(0).getValue()); } - @AfterAll - public static void afterClass() throws Exception { - ourServer.stop(); - } - @BeforeAll - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); - - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(0); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - ourServer.start(); - - ourPort = JettyUtil.getPortForStartedServer(ourServer); + @BeforeEach + void beforeEach() { + ourCtx = FhirContext.forDstu2(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + String ourServerBase = "http://localhost:" + port + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient.registerInterceptor(new LoggingInterceptor(true)); } - - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java index 5ff9de5..208ca01 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu3IT.java @@ -7,9 +7,6 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -18,39 +15,44 @@ import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.instance.model.api.IIdType; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; import java.net.URI; -import java.nio.file.Paths; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static ca.uhn.fhir.util.TestUtil.waitForSize; import static org.junit.jupiter.api.Assertions.assertEquals; +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = + { + "spring.batch.job.enabled=false", + "spring.profiles.active=dstu3", + "spring.datasource.url=jdbc:h2:mem:dbr3", + "hapi.fhir.subscription.websocket_enabled=true", + "hapi.fhir.allow_external_references=true", + "hapi.fhir.allow_placeholder_references=true", + }) + + public class ExampleServerDstu3IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu3IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private IGenericClient ourClient; + private FhirContext ourCtx; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU3"); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr3"); - HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); - HapiProperties.setProperty(HapiProperties.ALLOW_EXTERNAL_REFERENCES, "true"); - HapiProperties.setProperty(HapiProperties.ALLOW_PLACEHOLDER_REFERENCES, "true"); - ourCtx = FhirContext.forDstu3(); - } + @LocalServerPort + private int port; @Test public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + String methodName = "testCreateResourceConditional"; Patient pt = new Patient(); @@ -90,7 +92,7 @@ public class ExampleServerDstu3IT { SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); + URI echoUri = new URI("ws://localhost:" + port + "/hapi-fhir-jpaserver/websocket"); ClientUpgradeRequest request = new ClientUpgradeRequest(); ourLog.info("Connecting to : {}", echoUri); Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); @@ -119,39 +121,15 @@ public class ExampleServerDstu3IT { ourClient.delete().resourceById(mySubscriptionId).execute(); } - @AfterAll - public static void afterClass() throws Exception { - ourServer.stop(); - } - - @BeforeAll - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); - - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(0); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - ourServer.start(); - - ourPort = JettyUtil.getPortForStartedServer(ourServer); + @BeforeEach + void beforeEach() { + ourCtx = FhirContext.forDstu3(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; + String ourServerBase = "http://localhost:" + port + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient.registerInterceptor(new LoggingInterceptor(true)); } - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java index c610481..dbcfff4 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4IT.java @@ -7,26 +7,20 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; import ca.uhn.fhir.util.BundleUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.Observation; -import org.hl7.fhir.r4.model.Patient; -import org.hl7.fhir.r4.model.Person; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Subscription; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.hl7.fhir.r4.model.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; import java.net.URI; -import java.nio.file.Paths; import java.util.Collection; import java.util.List; import java.util.Optional; @@ -38,54 +32,58 @@ import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = + { + "spring.batch.job.enabled=false", + "spring.profiles.active=r4", + "spring.datasource.url=jdbc:h2:mem:dbr4", + "hapi.fhir.subscription.websocket_enabled=true", + "hapi.fhir.empi_enabled=true", + //Override is currently required when using Empi as the construction of the Empi beans are ambiguous as they are constructed multiple places. This is evident when running in a spring boot environment + "spring.main.allow-bean-definition-overriding=true" + }) public class ExampleServerR4IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private IGenericClient ourClient; + private FhirContext ourCtx; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4"); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); - HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); - HapiProperties.setProperty(HapiProperties.EMPI_ENABLED, "true"); - ourCtx = FhirContext.forR4(); - } + @LocalServerPort + private int port; - @Test - public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); - String methodName = "testCreateResourceConditional"; - Patient pt = new Patient(); - pt.setActive(true); - pt.getBirthDateElement().setValueAsString("2020-01-01"); - pt.addIdentifier().setSystem("http://foo").setValue("12345"); - pt.addName().setFamily(methodName); - IIdType id = ourClient.create().resource(pt).execute().getId(); + @Test + void testCreateAndRead() { - Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); - assertEquals(methodName, pt2.getName().get(0).getFamily()); + String methodName = "testCreateResourceConditional"; - // Test EMPI + Patient pt = new Patient(); + pt.setActive(true); + pt.getBirthDateElement().setValueAsString("2020-01-01"); + pt.addIdentifier().setSystem("http://foo").setValue("12345"); + pt.addName().setFamily(methodName); + IIdType id = ourClient.create().resource(pt).execute().getId(); - // Wait until the EMPI message has been processed - await().until(() -> getPeople().size() > 0); - List persons = getPeople(); + Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); + assertEquals(methodName, pt2.getName().get(0).getFamily()); - // Verify a Person was created that links to our Patient - Optional personLinkToCreatedPatient = persons.stream() - .map(Person::getLink) - .flatMap(Collection::stream) - .map(Person.PersonLinkComponent::getTarget) - .map(Reference::getReference) - .filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid)) - .findAny(); - assertTrue(personLinkToCreatedPatient.isPresent()); - } + // Test EMPI + + // Wait until the EMPI message has been processed + await().until(() -> getPeople().size() > 0); + List persons = getPeople(); + + // Verify a Person was created that links to our Patient + Optional personLinkToCreatedPatient = persons.stream() + .map(Person::getLink) + .flatMap(Collection::stream) + .map(Person.PersonLinkComponent::getTarget) + .map(Reference::getReference) + .filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid)) + .findAny(); + assertTrue(personLinkToCreatedPatient.isPresent()); + } private List getPeople() { Bundle bundle = ourClient.search().forResource(Person.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute(); @@ -93,103 +91,76 @@ public class ExampleServerR4IT { } @Test - public void testWebsocketSubscription() throws Exception { - /* - * Create subscription - */ - Subscription subscription = new Subscription(); - subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); - subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); - subscription.setCriteria("Observation?status=final"); + public void testWebsocketSubscription() throws Exception { + /* + * Create subscription + */ + Subscription subscription = new Subscription(); + subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); + subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); + subscription.setCriteria("Observation?status=final"); - Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); - channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); - channel.setPayload("application/json"); - subscription.setChannel(channel); + Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); + channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); + channel.setPayload("application/json"); + subscription.setChannel(channel); - MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); - IIdType mySubscriptionId = methodOutcome.getId(); + MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); + IIdType mySubscriptionId = methodOutcome.getId(); - // Wait for the subscription to be activated - await().until(() -> activeSubscriptionCount() == 3); + // Wait for the subscription to be activated + await().until(() -> activeSubscriptionCount() == 3); - /* - * Attach websocket - */ + /* + * Attach websocket + */ - WebSocketClient myWebSocketClient = new WebSocketClient(); - SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); + WebSocketClient myWebSocketClient = new WebSocketClient(); + SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); - myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); - ClientUpgradeRequest request = new ClientUpgradeRequest(); - ourLog.info("Connecting to : {}", echoUri); - Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); - Session session = connection.get(2, TimeUnit.SECONDS); + myWebSocketClient.start(); + URI echoUri = new URI("ws://localhost:" + port + "/hapi-fhir-jpaserver/websocket"); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + ourLog.info("Connecting to : {}", echoUri); + Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); + Session session = connection.get(2, TimeUnit.SECONDS); - ourLog.info("Connected to WS: {}", session.isOpen()); + ourLog.info("Connected to WS: {}", session.isOpen()); - /* - * Create a matching resource - */ - Observation obs = new Observation(); - obs.setStatus(Observation.ObservationStatus.FINAL); - ourClient.create().resource(obs).execute(); + /* + * Create a matching resource + */ + Observation obs = new Observation(); + obs.setStatus(Observation.ObservationStatus.FINAL); + ourClient.create().resource(obs).execute(); - // Give some time for the subscription to deliver - Thread.sleep(2000); + // Give some time for the subscription to deliver + Thread.sleep(2000); - /* - * Ensure that we receive a ping on the websocket - */ - waitForSize(1, () -> mySocketImplementation.myPingCount); + /* + * Ensure that we receive a ping on the websocket + */ + waitForSize(1, () -> mySocketImplementation.myPingCount); - /* - * Clean up - */ - ourClient.delete().resourceById(mySubscriptionId).execute(); - } + /* + * Clean up + */ + ourClient.delete().resourceById(mySubscriptionId).execute(); + } private int activeSubscriptionCount() { return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size(); } - @AfterAll - public static void afterClass() throws Exception { - ourServer.stop(); - } - @BeforeAll - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); + @BeforeEach + void beforeEach() { - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(0); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDisplayName("HAPI FHIR"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - ourServer.start(); - - ourPort = JettyUtil.getPortForStartedServer(ourServer); - - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = HapiProperties.getServerAddress(); - ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; - - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); - } - - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } + ourCtx = FhirContext.forR4(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/hapi-fhir-jpaserver/fhir/"; + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } } 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 ea9855f..9eea4d6 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR5IT.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.jpa.starter; +import static ca.uhn.fhir.util.TestUtil.waitForSize; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.EncodingEnum; @@ -7,9 +11,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.test.utilities.JettyUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; +import java.net.URI; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -20,38 +24,34 @@ import org.hl7.fhir.r5.model.Observation; import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Subscription; import org.hl7.fhir.r5.model.SubscriptionTopic; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.net.URI; -import java.nio.file.Paths; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import static ca.uhn.fhir.util.TestUtil.waitForSize; -import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertEquals; - +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = + { + "spring.batch.job.enabled=false", + "spring.profiles.active=r5", + "spring.datasource.url=jdbc:h2:mem:dbr5", + "hapi.fhir.subscription.websocket_enabled=true" + }) public class ExampleServerR5IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR5IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private IGenericClient ourClient; + private FhirContext ourCtx; + + @LocalServerPort + private int port; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr5"); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R5"); - HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); - ourCtx = FhirContext.forR5(); - } @Test public void testCreateAndRead() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + String methodName = "testCreateResourceConditional"; Patient pt = new Patient(); @@ -97,7 +97,7 @@ public class ExampleServerR5IT { SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); myWebSocketClient.start(); - URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); + URI echoUri = new URI("ws://localhost:" + port + "/hapi-fhir-jpaserver/websocket"); ClientUpgradeRequest request = new ClientUpgradeRequest(); ourLog.info("Connecting to : {}", echoUri); Future connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); @@ -115,7 +115,7 @@ public class ExampleServerR5IT { /* * Ensure that we receive a ping on the websocket */ - await().until(()->mySocketImplementation.myPingCount > 0); + await().until(() -> mySocketImplementation.myPingCount > 0); /* * Clean up @@ -123,41 +123,14 @@ public class ExampleServerR5IT { ourClient.delete().resourceById(mySubscriptionId).execute(); } - @AfterAll - public static void afterClass() throws Exception { - ourServer.stop(); - } - - @BeforeAll - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); - - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(0); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDisplayName("HAPI FHIR"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - ourServer.start(); - - ourPort = JettyUtil.getPortForStartedServer(ourServer); + @BeforeEach + void beforeEach() { + ourCtx = FhirContext.forR5(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; - + String ourServerBase = "http://localhost:" + port + "/hapi-fhir-jpaserver/fhir/"; ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient.registerInterceptor(new LoggingInterceptor(true)); } - - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java index 25e7315..856ad70 100644 --- a/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java +++ b/src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java @@ -7,44 +7,42 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor; import ca.uhn.fhir.rest.server.provider.ProviderConstants; -import ca.uhn.fhir.test.utilities.JettyUtil; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; -import org.hl7.fhir.r4.model.Bundle; -import org.hl7.fhir.r4.model.CodeType; -import org.hl7.fhir.r4.model.IntegerType; -import org.hl7.fhir.r4.model.Parameters; -import org.hl7.fhir.r4.model.Patient; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; +import org.hl7.fhir.r4.model.*; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; - -import java.nio.file.Paths; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit.jupiter.SpringExtension; import static org.junit.jupiter.api.Assertions.assertEquals; +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties = + { + "spring.batch.job.enabled=false", + "spring.profiles.active=r4", + "spring.datasource.url=jdbc:h2:mem:dbr4-mt", + "hapi.fhir.subscription.websocket_enabled=true", + "hapi.fhir.partitioning.partitioning_include_in_search_hashes=false" + + }) public class MultitenantServerR4IT { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantServerR4IT.class); - private static IGenericClient ourClient; - private static FhirContext ourCtx; - private static int ourPort; - private static Server ourServer; + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); + private IGenericClient ourClient; + private FhirContext ourCtx; + + @LocalServerPort + private int port; + private static UrlTenantSelectionInterceptor ourClientTenantInterceptor; - static { - HapiProperties.forceReload(); - HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4-mt"); - HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4"); - HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true"); - HapiProperties.setProperty(HapiProperties.PARTITIONING_ENABLED, "true"); - HapiProperties.setProperty(HapiProperties.PARTITIONING_MULTITENANCY_ENABLED, "true"); - ourCtx = FhirContext.forR4(); - } @Test public void testCreateAndReadInTenantA() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + // Create tenant A ourClientTenantInterceptor.setTenantId("DEFAULT"); @@ -70,7 +68,7 @@ public class MultitenantServerR4IT { @Test public void testCreateAndReadInTenantB() { - ourLog.info("Base URL is: " + HapiProperties.getServerAddress()); + // Create tenant A ourClientTenantInterceptor.setTenantId("DEFAULT"); @@ -94,45 +92,16 @@ public class MultitenantServerR4IT { assertEquals("Family B", pt2.getName().get(0).getFamily()); } - @AfterAll - public static void afterClass() throws Exception { - ourServer.stop(); - } - - @BeforeAll - public static void beforeClass() throws Exception { - String path = Paths.get("").toAbsolutePath().toString(); - - ourLog.info("Project base path is: {}", path); - - ourServer = new Server(0); - - WebAppContext webAppContext = new WebAppContext(); - webAppContext.setContextPath("/hapi-fhir-jpaserver"); - webAppContext.setDisplayName("HAPI FHIR"); - webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml"); - webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter"); - webAppContext.setParentLoaderPriority(true); - - ourServer.setHandler(webAppContext); - ourServer.start(); - - ourPort = JettyUtil.getPortForStartedServer(ourServer); - - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - String ourServerBase = HapiProperties.getServerAddress(); - ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; - - ourClient = ourCtx.newRestfulGenericClient(ourServerBase); - ourClient.registerInterceptor(new LoggingInterceptor(true)); + @BeforeEach + void beforeEach() { ourClientTenantInterceptor = new UrlTenantSelectionInterceptor(); + ourCtx = FhirContext.forR4(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/hapi-fhir-jpaserver/fhir/"; + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); ourClient.registerInterceptor(ourClientTenantInterceptor); } - - public static void main(String[] theArgs) throws Exception { - ourPort = 8080; - beforeClass(); - } } diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/RandomServerPortProvider.java b/src/test/java/ca/uhn/fhir/jpa/starter/RandomServerPortProvider.java deleted file mode 100644 index 65446f0..0000000 --- a/src/test/java/ca/uhn/fhir/jpa/starter/RandomServerPortProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package ca.uhn.fhir.jpa.starter; - -import java.io.IOException; -import java.net.ServerSocket; -import java.util.ArrayList; -import java.util.List; - -/** - * Provides server ports - */ -public class RandomServerPortProvider { - - private static List ourPorts = new ArrayList(); - - public static int findFreePort() { - ServerSocket server; - try { - server = new ServerSocket(0); - int port = server.getLocalPort(); - ourPorts.add(port); - server.close(); - Thread.sleep(500); - return port; - } catch (IOException e) { - throw new Error(e); - } catch (InterruptedException e) { - throw new Error(e); - } - } - - public static List list() { - return ourPorts; - } - -} - \ No newline at end of file diff --git a/src/test/resources/application-integrationtest.yaml b/src/test/resources/application-integrationtest.yaml new file mode 100644 index 0000000..038a343 --- /dev/null +++ b/src/test/resources/application-integrationtest.yaml @@ -0,0 +1,104 @@ +spring: + datasource: + url: 'jdbc:h2:file:./target/database/h2' + username: sa + password: null + driverClassName: org.h2.Driver + max-active: 15 + profiles: + ### This is the FHIR version. Choose between, dstu2, dstu3, r4 or r5 + active: r4 + +hapi: + fhir: + #supported_resource_types: + # - Patient + # - Observation +# allow_cascading_deletes: true +# allow_contains_searches: true +# allow_external_references: true +# allow_multiple_delete: true +# allow_override_default_search_params: true +# allow_placeholder_references: true +# auto_create_placeholder_reference_targets: false +# default_encoding: JSON +# default_pretty_print: true +# default_page_size: 20 +# enable_index_missing_fields: false +# enforce_referential_integrity_on_delete: false +# enforce_referential_integrity_on_write: false +# etag_support_enabled: true +# expunge_enabled: true +# daoconfig_client_id_strategy: null +# fhirpath_interceptor_enabled: false +# filter_search_enabled: true +# graphql_enabled: true + #partitioning: + # cross_partition_reference_mode: true + # multitenancy_enabled: true + # partitioning_include_in_search_hashes: true + #cors: + # allow_Credentials: true + # Supports multiple, comma separated allowed origin entries + # cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca + # allowed_origin: + # - '*' + +# logger: +# error_format: 'ERROR - ${requestVerb} ${requestUrl}' +# format: >- +# Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] +# Operation[${operationType} ${operationName} ${idOrResourceName}] +# UA[${requestHeader.user-agent}] Params[${requestParameters}] +# ResponseEncoding[${responseEncodingNoDefault}] +# log_exceptions: true +# name: fhirtest.access +# max_binary_size: 104857600 +# max_page_size: 200 +# retain_cached_searches_mins: 60 +# reuse_cached_search_results_millis: 60000 + tester: + - + id: home + name: Local Tester + server_address: 'http://localhost:8080/hapi-fhir-jpaserver/fhir' + refuse_to_fetch_third_party_urls: false + fhir_version: R4 + - + id: global + name: Global Tester + server_address: "http://hapi.fhir.org/baseR4" + refuse_to_fetch_third_party_urls: false + fhir_version: R4 +# validation: +# requests_enabled: true +# responses_enabled: true +# binary_storage_enabled: true +# bulk_export_enabled: true +# partitioning_multitenancy_enabled: +# subscription: +# resthook_enabled: false +# websocket_enabled: false +# email: +# from: some@test.com +# host: google.com +# port: +# username: +# password: +# auth: +# startTlsEnable: +# startTlsRequired: +# quitWait: + + +# +#elasticsearch: +# debug: +# pretty_print_json_log: false +# refresh_after_write: false +# enabled: false +# password: SomePassword +# required_index_status: YELLOW +# rest_url: 'http://localhost:9200' +# schema_management_strategy: CREATE +# username: SomeUsername