From 06d56daf7c0b5d11af01f6a797da20d40a29597e Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen Date: Fri, 30 Oct 2020 21:35:29 +0100 Subject: [PATCH 01/10] Update README.md Docker distroless example --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index c5be768..7ee6092 100644 --- a/README.md +++ b/README.md @@ -313,3 +313,24 @@ elasticsearch.password=SomePassword elasticsearch.required_index_status=YELLOW elasticsearch.schema_management_strategy=CREATE ``` + +## Example of a Dockerfile based on distroless images (for lower footprint and improved security) + +```code +FROM maven:3.6.3-jdk-11-slim as build-hapi +WORKDIR /tmp/hapi-fhir-jpaserver-starter + +COPY pom.xml . +RUN mvn -ntp dependency:go-offline + +COPY src/ /tmp/hapi-fhir-jpaserver-starter/src/ +RUN mvn clean package spring-boot:repackage -Pboot + +FROM gcr.io/distroless/java:11 + +COPY --from=build-hapi /tmp/hapi-fhir-jpaserver-starter/target/ROOT.war /app/main.war + +EXPOSE 8080 +WORKDIR /app +CMD ["main.war"] +``` From 77a98ca4380e4d97412bc9c506d7fd5dbac62176 Mon Sep 17 00:00:00 2001 From: jvi Date: Fri, 27 Nov 2020 14:49:53 +0100 Subject: [PATCH 02/10] Converted to named indicies in order to support easier environment configuration for both tester and IG setup --- .../uhn/fhir/jpa/starter/AppProperties.java | 20 +++++++------ .../jpa/starter/BaseJpaRestfulServer.java | 15 ++++------ .../fhir/jpa/starter/FhirTesterConfig.java | 12 ++++---- src/main/resources/application.yaml | 29 ++++++++++--------- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index 2835656..b5d7b8d 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -7,8 +7,10 @@ import ca.uhn.fhir.rest.api.EncodingEnum; import java.util.ArrayList; import java.util.List; +import java.util.Map; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import org.hl7.fhir.r4.model.Bundle; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -54,12 +56,12 @@ public class AppProperties { private List allowed_bundle_types = null; private Validation validation = new Validation(); - private List tester = ImmutableList.of(new Tester()); + private Map tester = ImmutableMap.of("home", new Tester()); private Logger logger = new Logger(); private Subscription subscription = new Subscription(); private Cors cors = null; private Partitioning partitioning = null; - private List implementationGuides = null; + private Map implementationGuides = null; public Integer getDefer_indexing_for_codesystems_of_size() { return defer_indexing_for_codesystems_of_size; @@ -69,11 +71,11 @@ public class AppProperties { this.defer_indexing_for_codesystems_of_size = defer_indexing_for_codesystems_of_size; } - public List getImplementationGuides() { + public Map getImplementationGuides() { return implementationGuides; } - public void setImplementationGuides(List implementationGuides) { + public void setImplementationGuides(Map implementationGuides) { this.implementationGuides = implementationGuides; } @@ -363,11 +365,11 @@ public class AppProperties { this.reuse_cached_search_results_millis = reuse_cached_search_results_millis; } - public List getTester() { + public Map getTester() { return tester; } - public void setTester(List tester) { + public void setTester(Map tester) { this.tester = tester; } @@ -437,7 +439,7 @@ public class AppProperties { public static class Tester { - private String id = "home"; + //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; @@ -451,14 +453,14 @@ public class AppProperties { this.fhir_version = fhir_version; } - public String getId() { +/* public String getId() { return id; } public void setId(String id) { this.id = id; } - +*/ public String getName() { return name; } 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 41743a3..18c4a87 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java @@ -38,10 +38,7 @@ 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.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; public class BaseJpaRestfulServer extends RestfulServer { @@ -345,12 +342,12 @@ public class BaseJpaRestfulServer extends RestfulServer { } if (appProperties.getImplementationGuides() != null) { - List guides = appProperties.getImplementationGuides(); - for (AppProperties.ImplementationGuide guide : guides) { + Map guides = appProperties.getImplementationGuides(); + for (Map.Entry guide : guides.entrySet()) { packageInstallerSvc.install(new PackageInstallationSpec() - .setPackageUrl(guide.getUrl()) - .setName(guide.getName()) - .setVersion(guide.getVersion()) + .setPackageUrl(guide.getValue().getUrl()) + .setName(guide.getValue().getName()) + .setVersion(guide.getValue().getVersion()) .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); } } 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 45a7808..7adf8bf 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirTesterConfig.java @@ -36,15 +36,15 @@ public class FhirTesterConfig { @Bean public TesterConfig testerConfig(AppProperties appProperties) { TesterConfig retVal = new TesterConfig(); - appProperties.getTester().stream().forEach(t -> { + appProperties.getTester().entrySet().stream().forEach(t -> { retVal .addServer() - .withId(t.getId()) - .withFhirVersion(t.getFhir_version()) - .withBaseUrl(t.getServer_address()) - .withName(t.getName()); + .withId(t.getKey()) + .withFhirVersion(t.getValue().getFhir_version()) + .withBaseUrl(t.getValue().getServer_address()) + .withName(t.getValue().getName()); retVal.setRefuseToFetchThirdPartyUrls( - t.getRefuse_to_fetch_third_party_urls()); + t.getValue().getRefuse_to_fetch_third_party_urls()); }); return retVal; diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index abb95b2..3a964f1 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,6 +1,7 @@ spring: datasource: - url: 'jdbc:h2:file:./target/database/h2' + #url: 'jdbc:h2:file:./target/database/h2' + url: jdbc:h2:mem:test_mem username: sa password: null driverClassName: org.h2.Driver @@ -30,14 +31,16 @@ hapi: ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 fhir_version: R4 # 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 + implementationguides: + #example from registry (packages.fhir.org) + swiss: + name: swiss.mednet.fhir + version: 0.8.0 + #example not from registry + ips_0_3_0: + url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz + name: hl7.fhir.uv.ips + version: 1.0.0 #supported_resource_types: # - Patient @@ -86,14 +89,14 @@ hapi: # retain_cached_searches_mins: 60 # reuse_cached_search_results_millis: 60000 tester: - - - id: home + + home: name: Local Tester server_address: 'http://localhost:8080/fhir' refuse_to_fetch_third_party_urls: false fhir_version: R4 - - - id: global + + global: name: Global Tester server_address: "http://hapi.fhir.org/baseR4" refuse_to_fetch_third_party_urls: false From f08fb03d67bf1321974d37b6096c534ce721c6a3 Mon Sep 17 00:00:00 2001 From: jvi Date: Fri, 27 Nov 2020 14:52:56 +0100 Subject: [PATCH 03/10] Removed dead code --- src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index b5d7b8d..98d02d9 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -439,7 +439,6 @@ public class AppProperties { 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; @@ -453,14 +452,6 @@ public class AppProperties { this.fhir_version = fhir_version; } -/* public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } -*/ public String getName() { return name; } From 48cfa4500a43f28577f493594f1767b8ba1c9cb9 Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen <46567685+jvitrifork@users.noreply.github.com> Date: Sat, 28 Nov 2020 16:49:51 +0100 Subject: [PATCH 04/10] Update application.yaml Updated default values --- src/main/resources/application.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 3a964f1..e742030 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,7 +1,7 @@ spring: datasource: - #url: 'jdbc:h2:file:./target/database/h2' - url: jdbc:h2:mem:test_mem + url: 'jdbc:h2:file:./target/database/h2' + #url: jdbc:h2:mem:test_mem username: sa password: null driverClassName: org.h2.Driver @@ -33,9 +33,9 @@ hapi: # defer_indexing_for_codesystems_of_size: 101 implementationguides: #example from registry (packages.fhir.org) - swiss: - name: swiss.mednet.fhir - version: 0.8.0 + #swiss: + #name: swiss.mednet.fhir + #version: 0.8.0 #example not from registry ips_0_3_0: url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz From 0b34ae22ee20f9928418723654d0dc5ffae49730 Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen Date: Sun, 29 Nov 2020 10:16:34 +0100 Subject: [PATCH 05/10] Update application.yaml --- src/main/resources/application.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index e742030..7657a60 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -31,16 +31,16 @@ hapi: ### This is the FHIR version. Choose between, DSTU2, DSTU3, R4 or R5 fhir_version: R4 # defer_indexing_for_codesystems_of_size: 101 - implementationguides: + #implementationguides: #example from registry (packages.fhir.org) #swiss: #name: swiss.mednet.fhir #version: 0.8.0 #example not from registry - ips_0_3_0: - url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz - name: hl7.fhir.uv.ips - version: 1.0.0 + #ips_1_0_0: + #url: https://build.fhir.org/ig/HL7/fhir-ips/package.tgz + #name: hl7.fhir.uv.ips + #version: 1.0.0 #supported_resource_types: # - Patient From 81d723a1f757b1fba32aed447c56014f1caa82eb Mon Sep 17 00:00:00 2001 From: Vladimir Nemergut Date: Wed, 2 Dec 2020 12:29:09 +0100 Subject: [PATCH 06/10] Use the select2 webjar version from parent pom --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8c2b2bc..c8c804e 100644 --- a/pom.xml +++ b/pom.xml @@ -197,7 +197,6 @@ org.webjars select2 - 4.0.3 org.webjars.bower From 08765f2203af1acfabdb616726d2d7c9c375eeb8 Mon Sep 17 00:00:00 2001 From: Vladimir Nemergut Date: Wed, 2 Dec 2020 12:55:17 +0100 Subject: [PATCH 07/10] Remove webjars versions specified in parent pom --- pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pom.xml b/pom.xml index 8c2b2bc..ecf437a 100644 --- a/pom.xml +++ b/pom.xml @@ -177,37 +177,30 @@ org.webjars Eonasdan-bootstrap-datetimepicker - 4.17.43 org.webjars font-awesome - 5.8.2 org.webjars.bower awesome-bootstrap-checkbox - 1.0.1 org.webjars jstimezonedetect - 1.0.6 org.webjars select2 - 4.0.3 org.webjars.bower jquery - 3.3.1 org.webjars.bower moment - 2.15.1 From 2e2bdaed6749914f451dac966bde547726214ec8 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Fri, 4 Dec 2020 15:50:43 -0500 Subject: [PATCH 08/10] Added support for $lastn operation and fixed Elasticsearch indexing. --- README.md | 5 + .../uhn/fhir/jpa/starter/AppProperties.java | 10 ++ .../jpa/starter/BaseJpaRestfulServer.java | 6 + .../fhir/jpa/starter/EnvironmentHelper.java | 60 ++++++++ .../jpa/starter/FhirServerConfigDstu3.java | 15 ++ .../fhir/jpa/starter/FhirServerConfigR4.java | 15 ++ .../fhir/jpa/starter/FhirServerConfigR5.java | 16 ++ src/main/resources/application.yaml | 1 + .../jpa/starter/ElasticsearchLastNR4IT.java | 144 ++++++++++++++++++ 9 files changed, 272 insertions(+) create mode 100644 src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java diff --git a/README.md b/README.md index 13d8fe4..a6e5244 100644 --- a/README.md +++ b/README.md @@ -321,3 +321,8 @@ elasticsearch.password=SomePassword elasticsearch.required_index_status=YELLOW elasticsearch.schema_management_strategy=CREATE ``` + +## Enabling LastN + +Set `hapi.fhir.lastn_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable the $lastn operation on this server. Note that the $lastn operation relies on Elasticsearch, so for $lastn to work, indexing must be enabled using Elasticsearch. + diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java index 722f517..9c4b767 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/AppProperties.java @@ -62,6 +62,8 @@ public class AppProperties { private Partitioning partitioning = null; private List implementationGuides = null; + private Boolean lastn_enabled = false; + public Integer getDefer_indexing_for_codesystems_of_size() { return defer_indexing_for_codesystems_of_size; } @@ -382,6 +384,14 @@ public class AppProperties { this.narrative_enabled = narrative_enabled; } + public Boolean getLastn_enabled() { + return lastn_enabled; + } + + public void setLastn_enabled(Boolean lastn_enabled) { + this.lastn_enabled = lastn_enabled; + } + public static class Cors { private Boolean allow_Credentials = true; private List allowed_origin = ImmutableList.of("*"); 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 99fe658..d60f8bc 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/BaseJpaRestfulServer.java @@ -360,6 +360,12 @@ public class BaseJpaRestfulServer extends RestfulServer { .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL)); } } + + if (appProperties.getLastn_enabled()) { + daoConfig.setLastNEnabled(true); + } + + } diff --git a/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java b/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java index 4dbc122..6dd8311 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/EnvironmentHelper.java @@ -1,5 +1,8 @@ package ca.uhn.fhir.jpa.starter; +import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder; +import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus; +import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy; import org.springframework.core.env.CompositePropertySource; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.EnumerablePropertySource; @@ -35,9 +38,66 @@ public class EnvironmentHelper { properties.put(values[0], values[1]); }); } + + if (environment.getProperty("elasticsearch.enabled", Boolean.class) != null + && environment.getProperty("elasticsearch.enabled", Boolean.class) == true ){ + ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder(); + ElasticsearchIndexStatus requiredIndexStatus = environment.getProperty("elasticsearch.required_index_status", ElasticsearchIndexStatus.class); + if (requiredIndexStatus == null) { + builder.setRequiredIndexStatus(ElasticsearchIndexStatus.YELLOW); + } else { + builder.setRequiredIndexStatus(requiredIndexStatus); + } + + builder.setRestUrl(getElasticsearchServerUrl(environment)); + builder.setUsername(getElasticsearchServerUsername(environment)); + builder.setPassword(getElasticsearchServerPassword(environment)); + IndexSchemaManagementStrategy indexSchemaManagementStrategy = environment.getProperty("elasticsearch.schema_management_strategy", IndexSchemaManagementStrategy.class); + if (indexSchemaManagementStrategy == null) { + builder.setIndexSchemaManagementStrategy(IndexSchemaManagementStrategy.CREATE); + } else { + builder.setIndexSchemaManagementStrategy(indexSchemaManagementStrategy); + } + // pretty_print_json_log: false + Boolean refreshAfterWrite = environment.getProperty("elasticsearch.debug.refresh_after_write", Boolean.class); + if (refreshAfterWrite == null) { + builder.setDebugRefreshAfterWrite(false); + } else { + builder.setDebugRefreshAfterWrite(refreshAfterWrite); + } + // pretty_print_json_log: false + Boolean prettyPrintJsonLog = environment.getProperty("elasticsearch.debug.pretty_print_json_log", Boolean.class); + if (prettyPrintJsonLog == null) { + builder.setDebugPrettyPrintJsonLog(false); + } else { + builder.setDebugPrettyPrintJsonLog(prettyPrintJsonLog); + } + builder.apply(properties); + } + return properties; } + public static String getElasticsearchServerUrl(ConfigurableEnvironment environment) { + return environment.getProperty("elasticsearch.rest_url", String.class); + } + + public static String getElasticsearchServerUsername(ConfigurableEnvironment environment) { + return environment.getProperty("elasticsearch.username"); + } + + public static String getElasticsearchServerPassword(ConfigurableEnvironment environment) { + return environment.getProperty("elasticsearch.password"); + } + + public static Boolean isElasticsearchEnabled(ConfigurableEnvironment environment) { + if (environment.getProperty("elasticsearch.enabled", Boolean.class) != null) { + return environment.getProperty("elasticsearch.enabled", Boolean.class); + } else { + return false; + } + } + public static Map getPropertiesStartingWith(ConfigurableEnvironment aEnv, String aKeyPrefix) { Map result = new HashMap<>(); 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 02d51ea..07406b2 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigDstu3.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; @@ -63,4 +64,18 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 { return retVal; } + @Bean() + public ElasticsearchSvcImpl elasticsearchSvc() { + if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) { + String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment); + String elasticsearchHost = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://")+3, elasticsearchUrl.lastIndexOf(":")); + String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment); + String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment); + int elasticsearchPort = Integer.parseInt(elasticsearchUrl.substring(elasticsearchUrl.lastIndexOf(":")+1)); + return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUsername, elasticsearchPassword); + } else { + return null; + } + } + } 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 6734723..e415fea 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR4.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.jpa.config.BaseJavaConfigR4; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -66,4 +67,18 @@ public class FhirServerConfigR4 extends BaseJavaConfigR4 { return retVal; } + @Bean() + public ElasticsearchSvcImpl elasticsearchSvc() { + if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) { + String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment); + String elasticsearchHost = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://")+3, elasticsearchUrl.lastIndexOf(":")); + String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment); + String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment); + int elasticsearchPort = Integer.parseInt(elasticsearchUrl.substring(elasticsearchUrl.lastIndexOf(":")+1)); + return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUsername, elasticsearchPassword); + } else { + return null; + } + } + } 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 28e5ef5..6e5bd98 100644 --- a/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java +++ b/src/main/java/ca/uhn/fhir/jpa/starter/FhirServerConfigR5.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.starter; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.jpa.config.BaseJavaConfigR5; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; import ca.uhn.fhir.jpa.starter.annotations.OnR5Condition; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -66,4 +67,19 @@ public class FhirServerConfigR5 extends BaseJavaConfigR5 { return retVal; } + @Bean() + public ElasticsearchSvcImpl elasticsearchSvc() { + if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) { + String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment); + String elasticsearchHost = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://")+3, elasticsearchUrl.lastIndexOf(":")); + String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment); + String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment); + int elasticsearchPort = Integer.parseInt(elasticsearchUrl.substring(elasticsearchUrl.lastIndexOf(":")+1)); + return new ElasticsearchSvcImpl(elasticsearchHost, elasticsearchPort, elasticsearchUsername, elasticsearchPassword); + } else { + return null; + } + } + + } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index cc33d00..51e25ae 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -120,6 +120,7 @@ hapi: # startTlsEnable: # startTlsRequired: # quitWait: +# lastn_enabled: true # diff --git a/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java b/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java new file mode 100644 index 0000000..c3fb7f7 --- /dev/null +++ b/src/test/java/ca/uhn/fhir/jpa/starter/ElasticsearchLastNR4IT.java @@ -0,0 +1,144 @@ +package ca.uhn.fhir.jpa.starter; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; +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 org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +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.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import pl.allegro.tech.embeddedelasticsearch.EmbeddedElastic; +import pl.allegro.tech.embeddedelasticsearch.PopularProperties; + +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +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.datasource.url=jdbc:h2:mem:dbr4", + "hapi.fhir.fhir_version=r4", + "hapi.fhir.lastn_enabled=true", + "elasticsearch.enabled=true", + // Because the port is set randomly, we will set the rest_url using the Initializer. + // "elasticsearch.rest_url='http://localhost:9200'", + "elasticsearch.username=SomeUsername", + "elasticsearch.password=SomePassword" + }) +@ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class) +public class ElasticsearchLastNR4IT { + + private IGenericClient ourClient; + private FhirContext ourCtx; + + private static final String ELASTIC_VERSION = "6.5.4"; + private static EmbeddedElastic embeddedElastic; + + @Autowired + private ElasticsearchSvcImpl myElasticsearchSvc; + + @BeforeAll + public static void beforeClass() { + + embeddedElastic = null; + try { + embeddedElastic = EmbeddedElastic.builder() + .withElasticVersion(ELASTIC_VERSION) + .withSetting(PopularProperties.TRANSPORT_TCP_PORT, 0) + .withSetting(PopularProperties.HTTP_PORT, 0) + .withSetting(PopularProperties.CLUSTER_NAME, UUID.randomUUID()) + .withStartTimeout(60, TimeUnit.SECONDS) + .build() + .start(); + } catch (IOException | InterruptedException e) { + throw new ConfigurationException(e); + } + } + + @PreDestroy + public void stop() { + embeddedElastic.stop(); + } + + @LocalServerPort + private int port; + + @Test + void testLastN() throws IOException { + + Patient pt = new Patient(); + pt.addName().setFamily("Lastn").addGiven("Arthur"); + IIdType id = ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); + + Observation obs = new Observation(); + obs.getSubject().setReferenceElement(id); + String observationCode = "testobservationcode"; + String codeSystem = "http://testobservationcodesystem"; + obs.getCode().addCoding().setCode(observationCode).setSystem(codeSystem); + obs.setValue(new StringType(observationCode)); + Date effectiveDtm = new GregorianCalendar().getTime(); + obs.setEffective(new DateTimeType(effectiveDtm)); + obs.getCategoryFirstRep().addCoding().setCode("testcategorycode").setSystem("http://testcategorycodesystem"); + IIdType obsId = ourClient.create().resource(obs).execute().getId().toUnqualifiedVersionless(); + + myElasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX); + + Parameters output = ourClient.operation().onType(Observation.class).named("lastn") + .withParameter(Parameters.class, "max", new IntegerType(1)) + .andParameter("subject", new StringType("Patient/" + id.getIdPart())) + .execute(); + Bundle b = (Bundle) output.getParameter().get(0).getResource(); + assertEquals(1, b.getTotal()); + assertEquals(obsId, b.getEntry().get(0).getResource().getIdElement().toUnqualifiedVersionless()); + } + + @BeforeEach + void beforeEach() { + + ourCtx = FhirContext.forR4(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + String ourServerBase = "http://localhost:" + port + "/fhir/"; + ourClient = ourCtx.newRestfulGenericClient(ourServerBase); + ourClient.registerInterceptor(new LoggingInterceptor(true)); + } + + static class Initializer + implements ApplicationContextInitializer { + + @Override + public void initialize( + ConfigurableApplicationContext configurableApplicationContext) { + // Since the port is dynamically generated, replace the URL with one that has the correct port + TestPropertyValues.of("elasticsearch.rest_url=http://localhost:" + embeddedElastic.getHttpPort()) + .applyTo(configurableApplicationContext.getEnvironment()); + } + + } +} From aa10461ce67f6c4517eb358b9e66ef82d3869a34 Mon Sep 17 00:00:00 2001 From: Jens Kristian Villadsen <46567685+jvitrifork@users.noreply.github.com> Date: Sat, 5 Dec 2020 21:02:27 +0100 Subject: [PATCH 09/10] Create maven.yml --- .github/workflows/maven.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..74c342f --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,24 @@ +# This workflow will build a Java project with Maven +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + - name: Build with Maven + run: mvn -B package --file pom.xml From 92656c80a23a780fb557b1962433102bb60060f1 Mon Sep 17 00:00:00 2001 From: ianmarshall Date: Thu, 10 Dec 2020 18:18:33 -0500 Subject: [PATCH 10/10] Clarify instructions for application server. --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 09c080d..b2f1e2e 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,20 @@ Again, browse to the following link to use the server (note that the port 8080 m [http://localhost:8080/](http://localhost:8080/) -If you would like it to be hosted at eg. hapi-fhir-jpaserver, eg. http://localhost:8080/hapi-fhir-jpaserver/ - then rename the WAR file to ```hapi-fhir-jpaserver.war```. +You will then be able access the JPA server e.g. using http://localhost:8080/fhir/metadata. + +If you would like it to be hosted at eg. hapi-fhir-jpaserver, eg. http://localhost:8080/hapi-fhir-jpaserver/ or http://localhost:8080/hapi-fhir-jpaserver/fhir/metadata - then rename the WAR file to ```hapi-fhir-jpaserver.war``` and adjust the overlay configuration accordingly e.g. + +```yaml + 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 +``` + ## Deploy with docker compose