Merge remote-tracking branch 'origin/master' into rel_8_3_tracking

This commit is contained in:
dotasek
2025-08-22 15:21:43 -04:00
36 changed files with 301 additions and 301 deletions

1
.gitignore vendored
View File

@@ -124,7 +124,6 @@ local.properties
.factorypath .factorypath
.project .project
.settings .settings
.springBeans
.sts4-cache .sts4-cache
# Code Recommenders # Code Recommenders

View File

@@ -51,7 +51,7 @@ HAPI looks in the environment variables for properties in the [application.yaml]
### Configuration via overridden application.yaml file and using Docker ### Configuration via overridden application.yaml file and using Docker
You can customize HAPI by telling HAPI to look for the configuration file in a different location, eg.: You can customize HAPI by telling HAPI to look for the configuration file in a different location, e.g.:
``` ```
docker run -p 8090:8080 -v $(pwd)/yourLocalFolder:/configs -e "--spring.config.location=file:///configs/another.application.yaml" hapiproject/hapi:latest docker run -p 8090:8080 -v $(pwd)/yourLocalFolder:/configs -e "--spring.config.location=file:///configs/another.application.yaml" hapiproject/hapi:latest
@@ -237,7 +237,7 @@ The Server will then be accessible at http://localhost:8888/fhir and the Capabil
```bash ```bash
mvn clean spring-boot:run -Pboot mvn clean spring-boot:run -Pboot
``` ```
Server will then be accessible at http://localhost:8080/ and eg. http://localhost:8080/fhir/metadata. Remember to adjust you overlay configuration in the application.yaml to the following: Server will then be accessible at http://localhost:8080/ and e.g. http://localhost:8080/fhir/metadata. Remember to adjust you overlay configuration in the application.yaml to the following:
```yaml ```yaml
tester: tester:
@@ -253,7 +253,7 @@ Server will then be accessible at http://localhost:8080/ and eg. http://localhos
```bash ```bash
mvn clean package spring-boot:repackage -DskipTests=true -Pboot && java -jar target/ROOT.war mvn clean package spring-boot:repackage -DskipTests=true -Pboot && java -jar target/ROOT.war
``` ```
Server will then be accessible at http://localhost:8080/ and eg. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following: Server will then be accessible at http://localhost:8080/ and e.g. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following:
```yaml ```yaml
tester: tester:
@@ -268,7 +268,7 @@ Server will then be accessible at http://localhost:8080/ and eg. http://localhos
```bash ```bash
mvn clean package com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=distroless-hapi && docker run -p 8080:8080 distroless-hapi mvn clean package com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=distroless-hapi && docker run -p 8080:8080 distroless-hapi
``` ```
Server will then be accessible at http://localhost:8080/ and eg. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following: Server will then be accessible at http://localhost:8080/ and e.g. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following:
```yaml ```yaml
tester: tester:
@@ -284,7 +284,7 @@ Server will then be accessible at http://localhost:8080/ and eg. http://localhos
```bash ```bash
./build-docker-image.sh && docker run -p 8080:8080 hapi-fhir/hapi-fhir-jpaserver-starter:latest ./build-docker-image.sh && docker run -p 8080:8080 hapi-fhir/hapi-fhir-jpaserver-starter:latest
``` ```
Server will then be accessible at http://localhost:8080/ and eg. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following: Server will then be accessible at http://localhost:8080/ and e.g. http://localhost:8080/fhir/metadata. Remember to adjust your overlay configuration in the application.yaml to the following:
```yaml ```yaml
tester: tester:
@@ -384,7 +384,7 @@ Several template files that can be customized are found in the following directo
Using the Maven-Embedded Jetty method above is convenient, but it is not a good solution if you want to leave the server running in the background. Using the Maven-Embedded Jetty method above is convenient, but it is not a good solution if you want to leave the server running in the background.
Most people who are using HAPI FHIR JPA as a server that is accessible to other people (whether internally on your network or publically hosted) will do so using an Application Server, such as [Apache Tomcat](http://tomcat.apache.org/) or [Jetty](https://www.eclipse.org/jetty/). Note that any Servlet 3.0+ compatible Web Container will work (e.g Wildfly, Websphere, etc.). Most people who are using HAPI FHIR JPA as a server that is accessible to other people (whether internally on your network or publicly hosted) will do so using an Application Server, such as [Apache Tomcat](http://tomcat.apache.org/) or [Jetty](https://www.eclipse.org/jetty/). Note that any Servlet 3.0+ compatible Web Container will work (e.g. Wildfly, Websphere, etc.).
Tomcat is very popular, so it is a good choice simply because you will be able to find many tutorials online. Jetty is a great alternative due to its fast startup time and good overall performance. Tomcat is very popular, so it is a good choice simply because you will be able to find many tutorials online. Jetty is a great alternative due to its fast startup time and good overall performance.
@@ -402,7 +402,7 @@ Again, browse to the following link to use the server (note that the port 8080 m
You will then be able to access the JPA server e.g. using http://localhost:8080/fhir/metadata. You will then be able to 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. If you would like it to be hosted at e.g. hapi-fhir-jpaserver, e.g. 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 ```yaml
tester: tester:
@@ -525,7 +525,7 @@ Set `hapi.fhir.store_resource_in_lucene_index_enabled` in the [application.yaml]
## Changing cached search results time ## Changing cached search results time
It is possible to change the cached search results time. The option `reuse_cached_search_results_millis` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) is 6000 miliseconds by default. It is possible to change the cached search results time. The option `reuse_cached_search_results_millis` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) is 6000 milliseconds by default.
Set `reuse_cached_search_results_millis: -1` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to ignore the cache time every search. Set `reuse_cached_search_results_millis: -1` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to ignore the cache time every search.
## Build the distroless variant of the image (for lower footprint and improved security) ## Build the distroless variant of the image (for lower footprint and improved security)

View File

@@ -14,7 +14,7 @@ dependencies:
repository: oci://registry-1.docker.io/bitnamicharts repository: oci://registry-1.docker.io/bitnamicharts
version: 2.31.3 version: 2.31.3
appVersion: 8.2.0 appVersion: 8.2.0
version: 0.20.0 version: 0.20.1
annotations: annotations:
artifacthub.io/license: Apache-2.0 artifacthub.io/license: Apache-2.0
artifacthub.io/containsSecurityUpdates: "false" artifacthub.io/containsSecurityUpdates: "false"
@@ -27,14 +27,4 @@ annotations:
# When using the list of objects option the valid supported kinds are # When using the list of objects option the valid supported kinds are
# added, changed, deprecated, removed, fixed, and security. # added, changed, deprecated, removed, fixed, and security.
- kind: changed - kind: changed
description: "updated postgresql sub-chart to 16.7.11" description: "fixed typo in README.md"
- kind: changed
description: "updated common sub-chart to 2.31.3"
- kind: changed
description: "updated curlimages/curl to 8.14.1"
- kind: changed
description: "updated hapiproject/hapi to v8.2.0-1"
- kind: changed
description: "use ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgresDialect dialect"
- kind: changed
description: "made the init container waiting for the database to be ready configurable"

View File

@@ -1,6 +1,6 @@
# HAPI FHIR JPA Server Starter Helm Chart # HAPI FHIR JPA Server Starter Helm Chart
![Version: 0.20.0](https://img.shields.io/badge/Version-0.20.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 8.2.0](https://img.shields.io/badge/AppVersion-8.2.0-informational?style=flat-square) ![Version: 0.20.1](https://img.shields.io/badge/Version-0.20.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 8.2.0](https://img.shields.io/badge/AppVersion-8.2.0-informational?style=flat-square)
This helm chart will help you install the HAPI FHIR JPA Server in a Kubernetes environment. This helm chart will help you install the HAPI FHIR JPA Server in a Kubernetes environment.
@@ -98,7 +98,7 @@ INFO[2021-11-20T12:38:04Z] Found Chart directories [charts/hapi-fhir-jpaserver]
INFO[2021-11-20T12:38:04Z] Generating README Documentation for chart /usr/src/app/charts/hapi-fhir-jpaserver INFO[2021-11-20T12:38:04Z] Generating README Documentation for chart /usr/src/app/charts/hapi-fhir-jpaserver
``` ```
## Enable Distributed Tracing based on the OpenTelemtry Java Agent ## Enable Distributed Tracing based on the OpenTelemetry Java Agent
The container image includes the [OpenTelemetry Java agent JAR](https://github.com/open-telemetry/opentelemetry-java-instrumentation) The container image includes the [OpenTelemetry Java agent JAR](https://github.com/open-telemetry/opentelemetry-java-instrumentation)
which can be used to enable distributed tracing. It can be configured entirely using environment variables, which can be used to enable distributed tracing. It can be configured entirely using environment variables,

View File

@@ -27,7 +27,7 @@ INFO[2021-11-20T12:38:04Z] Found Chart directories [charts/hapi-fhir-jpaserver]
INFO[2021-11-20T12:38:04Z] Generating README Documentation for chart /usr/src/app/charts/hapi-fhir-jpaserver INFO[2021-11-20T12:38:04Z] Generating README Documentation for chart /usr/src/app/charts/hapi-fhir-jpaserver
``` ```
## Enable Distributed Tracing based on the OpenTelemtry Java Agent ## Enable Distributed Tracing based on the OpenTelemetry Java Agent
The container image includes the [OpenTelemetry Java agent JAR](https://github.com/open-telemetry/opentelemetry-java-instrumentation) The container image includes the [OpenTelemetry Java agent JAR](https://github.com/open-telemetry/opentelemetry-java-instrumentation)
which can be used to enable distributed tracing. It can be configured entirely using environment variables, which can be used to enable distributed tracing. It can be configured entirely using environment variables,

View File

@@ -6,7 +6,7 @@
<properties> <properties>
<java.version>17</java.version> <java.version>17</java.version>
<hapi.fhir.jpa.server.starter.revision>1</hapi.fhir.jpa.server.starter.revision> <hapi.fhir.jpa.server.starter.revision>1</hapi.fhir.jpa.server.starter.revision>
<clinical-reasoning.version>3.23.0</clinical-reasoning.version> <clinical-reasoning.version>3.24.0</clinical-reasoning.version>
</properties> </properties>
<!-- one-liner to take you to the cloud with settings form the application.yaml file: --> <!-- one-liner to take you to the cloud with settings form the application.yaml file: -->
@@ -195,7 +195,7 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- If you are using HAPI narrative generation, you will need to include Thymeleaf as well. Otherwise the following can be omitted. --> <!-- If you are using HAPI narrative generation, you will need to include Thymeleaf as well. Otherwise, the following can be omitted. -->
<dependency> <dependency>
<groupId>org.thymeleaf</groupId> <groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId> <artifactId>thymeleaf</artifactId>

View File

@@ -45,7 +45,7 @@ public class Application extends SpringBootServletInitializer {
SpringApplication.run(Application.class, args); SpringApplication.run(Application.class, args);
// Server is now accessible at eg. http://localhost:8080/fhir/metadata // Server is now accessible at e.g. http://localhost:8080/fhir/metadata
// UI is now accessible at http://localhost:8080/ // UI is now accessible at http://localhost:8080/
} }

View File

@@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import java.io.IOException; import java.io.IOException;
import java.io.Serial;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.opencds.cqf.fhir.cr.hapi.config.test.TestCdsHooksConfig.CDS_HOOKS_OBJECT_MAPPER_FACTORY; import static org.opencds.cqf.fhir.cr.hapi.config.test.TestCdsHooksConfig.CDS_HOOKS_OBJECT_MAPPER_FACTORY;
@@ -29,6 +30,8 @@ import static org.opencds.cqf.fhir.cr.hapi.config.test.TestCdsHooksConfig.CDS_HO
@Configurable @Configurable
public class CdsHooksServlet extends HttpServlet { public class CdsHooksServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class);
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Autowired @Autowired

View File

@@ -179,9 +179,8 @@ public class ModuleConfigurationPrefetchSvc extends CdsPrefetchSvc {
return true; return true;
} }
if (resource instanceof IBaseBundle) { if (resource instanceof IBaseBundle) {
return BundleUtil.toListOfEntries(fhirContext, (IBaseBundle) resource) return !BundleUtil.toListOfEntries(fhirContext, (IBaseBundle) resource)
.size() .isEmpty();
> 0;
} }
return false; return false;
} }

View File

@@ -41,24 +41,28 @@ public class FhirServerConfigCommon {
private static final Logger ourLog = LoggerFactory.getLogger(FhirServerConfigCommon.class); private static final Logger ourLog = LoggerFactory.getLogger(FhirServerConfigCommon.class);
public FhirServerConfigCommon(AppProperties appProperties) { 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.getDao_scheduling_enabled() ? "enable" : "disable")
+ " DAO scheduling");
ourLog.info("Server configured to " + (appProperties.getDelete_expunge_enabled() ? "enable" : "disable")
+ " delete expunges");
ourLog.info( ourLog.info(
"Server configured to " + (appProperties.getExpunge_enabled() ? "enable" : "disable") + " expunges"); "Server configured to {} contains searches",
appProperties.getAllow_contains_searches() ? "allow" : "deny");
ourLog.info( ourLog.info(
"Server configured to " + (appProperties.getAllow_override_default_search_params() ? "allow" : "deny") "Server configured to {} multiple deletes",
+ " overriding default search params"); appProperties.getAllow_multiple_delete() ? "allow" : "deny");
ourLog.info("Server configured to " ourLog.info(
+ (appProperties.getAuto_create_placeholder_reference_targets() ? "allow" : "disable") "Server configured to {} external references",
+ " auto-creating placeholder references"); appProperties.getAllow_external_references() ? "allow" : "deny");
ourLog.info(
"Server configured to {} DAO scheduling",
appProperties.getDao_scheduling_enabled() ? "enable" : "disable");
ourLog.info(
"Server configured to {} delete expunges",
appProperties.getDelete_expunge_enabled() ? "enable" : "disable");
ourLog.info("Server configured to {} expunges", appProperties.getExpunge_enabled() ? "enable" : "disable");
ourLog.info(
"Server configured to {} overriding default search params",
appProperties.getAllow_override_default_search_params() ? "allow" : "deny");
ourLog.info(
"Server configured to {} auto-creating placeholder references",
appProperties.getAuto_create_placeholder_reference_targets() ? "allow" : "disable");
ourLog.info( ourLog.info(
"Server configured to auto-version references at paths {}", "Server configured to auto-version references at paths {}",
appProperties.getAuto_version_reference_at_paths()); appProperties.getAuto_version_reference_at_paths());
@@ -66,12 +70,14 @@ public class FhirServerConfigCommon {
if (appProperties.getSubscription().getEmail() != null) { if (appProperties.getSubscription().getEmail() != null) {
AppProperties.Subscription.Email email = AppProperties.Subscription.Email email =
appProperties.getSubscription().getEmail(); appProperties.getSubscription().getEmail();
ourLog.info("Server is configured to enable email with host '" + email.getHost() + "' and port " ourLog.info(
+ email.getPort()); "Server is configured to enable email with host '{}' and port {}",
ourLog.info("Server will use '" + email.getFrom() + "' as the from email address"); email.getHost(),
email.getPort());
ourLog.info("Server will use '{}' as the from email address", email.getFrom());
if (!Strings.isNullOrEmpty(email.getUsername())) { if (!Strings.isNullOrEmpty(email.getUsername())) {
ourLog.info("Server is configured to use username '" + email.getUsername() + "' for email"); ourLog.info("Server is configured to use username '{}' for email", email.getUsername());
} }
if (!Strings.isNullOrEmpty(email.getPassword())) { if (!Strings.isNullOrEmpty(email.getPassword())) {
@@ -91,17 +97,19 @@ public class FhirServerConfigCommon {
ourLog.info("Indexed on contained resource enabled"); ourLog.info("Indexed on contained resource enabled");
} }
ourLog.info("Server configured to " + (appProperties.getPre_expand_value_sets() ? "enable" : "disable")
+ " value set pre-expansion");
ourLog.info( ourLog.info(
"Server configured to " + (appProperties.getEnable_task_pre_expand_value_sets() ? "enable" : "disable") "Server configured to {} value set pre-expansion",
+ " value set pre-expansion task"); appProperties.getPre_expand_value_sets() ? "enable" : "disable");
ourLog.info("Server configured for pre-expand value set default count of " ourLog.info(
+ (appProperties.getPre_expand_value_sets_default_count().toString())); "Server configured to {} value set pre-expansion task",
ourLog.info("Server configured for pre-expand value set max count of " appProperties.getEnable_task_pre_expand_value_sets() ? "enable" : "disable");
+ (appProperties.getPre_expand_value_sets_max_count().toString())); ourLog.info(
ourLog.info("Server configured for maximum expansion size of " "Server configured for pre-expand value set default count of {}",
+ (appProperties.getMaximum_expansion_size().toString())); appProperties.getPre_expand_value_sets_default_count());
ourLog.info(
"Server configured for pre-expand value set max count of {}",
appProperties.getPre_expand_value_sets_max_count());
ourLog.info("Server configured for maximum expansion size of {}", appProperties.getMaximum_expansion_size());
} }
@Bean @Bean
@@ -194,8 +202,9 @@ public class FhirServerConfigCommon {
Integer maxFetchSize = appProperties.getMax_page_size(); Integer maxFetchSize = appProperties.getMax_page_size();
jpaStorageSettings.setFetchSizeDefaultMaximum(maxFetchSize); jpaStorageSettings.setFetchSizeDefaultMaximum(maxFetchSize);
ourLog.info("Server configured to have a maximum fetch size of " ourLog.info(
+ (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); "Server configured to have a maximum fetch size of {}",
maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize);
Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis(); Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis();
jpaStorageSettings.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); jpaStorageSettings.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis);
@@ -237,19 +246,21 @@ public class FhirServerConfigCommon {
// Set and/or recommend default Server ID Strategy of UUID when using the ANY Client ID Strategy // Set and/or recommend default Server ID Strategy of UUID when using the ANY Client ID Strategy
if (appProperties.getClient_id_strategy() == JpaStorageSettings.ClientIdStrategyEnum.ANY) { if (appProperties.getClient_id_strategy() == JpaStorageSettings.ClientIdStrategyEnum.ANY) {
if (appProperties.getServer_id_strategy() == null) { if (appProperties.getServer_id_strategy() == null) {
ourLog.info("Defaulting server to use '" + JpaStorageSettings.IdStrategyEnum.UUID ourLog.info(
+ "' Server ID Strategy when using the '" + JpaStorageSettings.ClientIdStrategyEnum.ANY "Defaulting server to use '{}' Server ID Strategy when using the '{}' Client ID Strategy",
+ "' Client ID Strategy"); JpaStorageSettings.IdStrategyEnum.UUID,
JpaStorageSettings.ClientIdStrategyEnum.ANY);
appProperties.setServer_id_strategy(JpaStorageSettings.IdStrategyEnum.UUID); appProperties.setServer_id_strategy(JpaStorageSettings.IdStrategyEnum.UUID);
} else if (appProperties.getServer_id_strategy() != JpaStorageSettings.IdStrategyEnum.UUID) { } else if (appProperties.getServer_id_strategy() != JpaStorageSettings.IdStrategyEnum.UUID) {
ourLog.warn("WARNING: '" + JpaStorageSettings.IdStrategyEnum.UUID ourLog.warn(
+ "' Server ID Strategy is highly recommended when using the '" "WARNING: '{}' Server ID Strategy is highly recommended when using the '{}' Client ID Strategy",
+ JpaStorageSettings.ClientIdStrategyEnum.ANY + "' Client ID Strategy"); JpaStorageSettings.IdStrategyEnum.UUID,
JpaStorageSettings.ClientIdStrategyEnum.ANY);
} }
} }
if (appProperties.getServer_id_strategy() != null) { if (appProperties.getServer_id_strategy() != null) {
jpaStorageSettings.setResourceServerIdStrategy(appProperties.getServer_id_strategy()); jpaStorageSettings.setResourceServerIdStrategy(appProperties.getServer_id_strategy());
ourLog.info("Server configured to use '" + appProperties.getServer_id_strategy() + "' Server ID Strategy"); ourLog.info("Server configured to use '{}' Server ID Strategy", appProperties.getServer_id_strategy());
} }
// to Disable the Resource History // to Disable the Resource History

View File

@@ -248,7 +248,7 @@ public class StarterJpaConfig {
List<String> allAllowedCORSOrigins = appProperties.getCors().getAllowed_origin(); List<String> allAllowedCORSOrigins = appProperties.getCors().getAllowed_origin();
allAllowedCORSOrigins.forEach(config::addAllowedOriginPattern); allAllowedCORSOrigins.forEach(config::addAllowedOriginPattern);
ourLog.info("CORS allows the following origins: " + String.join(", ", allAllowedCORSOrigins)); ourLog.info("CORS allows the following origins: {}", String.join(", ", allAllowedCORSOrigins));
config.addExposedHeader("Location"); config.addExposedHeader("Location");
config.addExposedHeader("Content-Location"); config.addExposedHeader("Content-Location");
@@ -467,9 +467,7 @@ public class StarterJpaConfig {
registerCustomInterceptors(fhirServer, appContext, appProperties.getCustomInterceptorClasses()); registerCustomInterceptors(fhirServer, appContext, appProperties.getCustomInterceptorClasses());
// register the IPS Provider // register the IPS Provider
if (!theIpsOperationProvider.isEmpty()) { theIpsOperationProvider.ifPresent(fhirServer::registerProvider);
fhirServer.registerProvider(theIpsOperationProvider.get());
}
if (appProperties.getUserRequestRetryVersionConflictsInterceptorEnabled()) { if (appProperties.getUserRequestRetryVersionConflictsInterceptorEnabled()) {
fhirServer.registerInterceptor(new UserRequestRetryVersionConflictsInterceptor()); fhirServer.registerInterceptor(new UserRequestRetryVersionConflictsInterceptor());
@@ -500,7 +498,7 @@ public class StarterJpaConfig {
throw new ConfigurationException("Interceptor class was not found on classpath: " + className, e); throw new ConfigurationException("Interceptor class was not found on classpath: " + className, e);
} }
// first check if the class a Bean in the app context // first check if the class is a Bean in the app context
Object interceptor = null; Object interceptor = null;
try { try {
interceptor = theAppContext.getBean(clazz); interceptor = theAppContext.getBean(clazz);
@@ -541,7 +539,7 @@ public class StarterJpaConfig {
throw new ConfigurationException("Provider class was not found on classpath: " + className, e); throw new ConfigurationException("Provider class was not found on classpath: " + className, e);
} }
// first check if the class a Bean in the app context // first check if the class is a Bean in the app context
Object provider = null; Object provider = null;
try { try {
provider = theAppContext.getBean(clazz); provider = theAppContext.getBean(clazz);

View File

@@ -50,11 +50,11 @@ public class RepositoryValidationInterceptorFactoryR4 implements IRepositoryVali
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap()
.setLoadSynchronous(true) .setLoadSynchronous(true)
.add(StructureDefinition.SP_KIND, new TokenParam("resource"))); .add(StructureDefinition.SP_KIND, new TokenParam("resource")));
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream() Map<String, List<StructureDefinition>> structureDefinitions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefinitions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder repositoryValidatingRuleBuilder
.forResourcesOfType(key) .forResourcesOfType(key)

View File

@@ -50,11 +50,11 @@ public class RepositoryValidationInterceptorFactoryR4B implements IRepositoryVal
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap()
.setLoadSynchronous(true) .setLoadSynchronous(true)
.add(StructureDefinition.SP_KIND, new TokenParam("resource"))); .add(StructureDefinition.SP_KIND, new TokenParam("resource")));
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream() Map<String, List<StructureDefinition>> structureDefinitions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefinitions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder repositoryValidatingRuleBuilder
.forResourcesOfType(key) .forResourcesOfType(key)

View File

@@ -49,11 +49,11 @@ public class RepositoryValidationInterceptorFactoryR5 implements IRepositoryVali
IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap()
.setLoadSynchronous(true) .setLoadSynchronous(true)
.add(StructureDefinition.SP_KIND, new TokenParam("resource"))); .add(StructureDefinition.SP_KIND, new TokenParam("resource")));
Map<String, List<StructureDefinition>> structureDefintions = results.getResources(0, results.size()).stream() Map<String, List<StructureDefinition>> structureDefinitions = results.getResources(0, results.size()).stream()
.map(StructureDefinition.class::cast) .map(StructureDefinition.class::cast)
.collect(Collectors.groupingBy(StructureDefinition::getType)); .collect(Collectors.groupingBy(StructureDefinition::getType));
structureDefintions.forEach((key, value) -> { structureDefinitions.forEach((key, value) -> {
String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new);
repositoryValidatingRuleBuilder repositoryValidatingRuleBuilder
.forResourcesOfType(key) .forResourcesOfType(key)

View File

@@ -22,6 +22,7 @@ import org.opencds.cqf.fhir.cr.hapi.common.ElmCacheResourceChangeListener;
import org.opencds.cqf.fhir.cr.measure.CareGapsProperties; import org.opencds.cqf.fhir.cr.measure.CareGapsProperties;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.utility.ValidationProfile; import org.opencds.cqf.fhir.utility.ValidationProfile;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClientSettings;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Conditional;
@@ -57,6 +58,11 @@ public class CrCommonConfig {
return theCrProperties.getCql().getTerminology(); return theCrProperties.getCql().getTerminology();
} }
@Bean
TerminologyServerClientSettings terminologyServerClientSettings(CrProperties theCrProperties) {
return theCrProperties.getTerminologyServerClientSettings();
}
@Bean @Bean
public EvaluationSettings evaluationSettings( public EvaluationSettings evaluationSettings(
CrProperties theCrProperties, CrProperties theCrProperties,

View File

@@ -1,11 +1,15 @@
package ca.uhn.fhir.jpa.starter.cr; package ca.uhn.fhir.jpa.starter.cr;
import org.opencds.cqf.fhir.utility.client.TerminologyServerClientSettings;
public class CrProperties { public class CrProperties {
private Boolean enabled; private Boolean enabled;
private CareGapsProperties careGaps = new CareGapsProperties(); private CareGapsProperties careGaps = new CareGapsProperties();
private CqlProperties cql = new CqlProperties(); private CqlProperties cql = new CqlProperties();
private TerminologyServerClientSettings terminologyServerClientSettings = new TerminologyServerClientSettings();
public Boolean getEnabled() { public Boolean getEnabled() {
return enabled; return enabled;
} }
@@ -29,4 +33,12 @@ public class CrProperties {
public void setCql(CqlProperties cql) { public void setCql(CqlProperties cql) {
this.cql = cql; this.cql = cql;
} }
public TerminologyServerClientSettings getTerminologyServerClientSettings() {
return terminologyServerClientSettings;
}
public void setTerminologyServerClientSettings(TerminologyServerClientSettings terminologyServerClientSettings) {
this.terminologyServerClientSettings = terminologyServerClientSettings;
}
} }

View File

@@ -11,7 +11,7 @@ public class PostInitProviderRegisterer {
resourceProviderFactory.attach(new Observer(restfulServer)); resourceProviderFactory.attach(new Observer(restfulServer));
} }
private class Observer implements IResourceProviderFactoryObserver { private static class Observer implements IResourceProviderFactoryObserver {
private RestfulServer restfulServer; private RestfulServer restfulServer;
public Observer(RestfulServer restfulServer) { public Observer(RestfulServer restfulServer) {

View File

@@ -188,14 +188,12 @@ public class EnvironmentHelper {
public static Map<String, Object> getAllProperties(PropertySource<?> aPropSource) { public static Map<String, Object> getAllProperties(PropertySource<?> aPropSource) {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
if (aPropSource instanceof CompositePropertySource) { if (aPropSource instanceof CompositePropertySource cps) {
CompositePropertySource cps = (CompositePropertySource) aPropSource;
cps.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps))); cps.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps)));
return result; return result;
} }
if (aPropSource instanceof EnumerablePropertySource<?>) { if (aPropSource instanceof EnumerablePropertySource<?> ps) {
EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource;
Arrays.asList(ps.getPropertyNames()).forEach(key -> result.put(key, ps.getProperty(key))); Arrays.asList(ps.getPropertyNames()).forEach(key -> result.put(key, ps.getProperty(key)));
return result; return result;
} }

View File

@@ -6,7 +6,7 @@ server:
tomcat: tomcat:
# allow | as a separator in the URL # allow | as a separator in the URL
relaxed-query-chars: "|" relaxed-query-chars: "|"
#Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #Adds the option to go to e.g. http://localhost:8080/actuator/health for seeing the running configuration
#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints
management: management:
#The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default.
@@ -14,7 +14,7 @@ management:
enabled-by-default: false enabled-by-default: false
web: web:
exposure: exposure:
include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all
endpoint: endpoint:
info: info:
enabled: true enabled: true
@@ -89,6 +89,11 @@ hapi:
caregaps: caregaps:
reporter: "default" reporter: "default"
section_author: "default" section_author: "default"
terminologyServerClientSettings:
maxRetryCount: 3
retryIntervalMillis: 1000
timeoutSeconds: 30
socketTimeout: 60
cql: cql:
use_embedded_libraries: true use_embedded_libraries: true
compiler: compiler:
@@ -288,12 +293,12 @@ hapi:
# comma-separated list of fully qualified interceptor classes. # comma-separated list of fully qualified interceptor classes.
# classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages',
# or will be instantiated via reflection using an no-arg contructor; then registered with the server # or will be instantiated via reflection using a no-arg constructor; then registered with the server
#custom-interceptor-classes: #custom-interceptor-classes:
# comma-separated list of fully qualified provider classes. # comma-separated list of fully qualified provider classes.
# classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages',
# or will be instantiated via reflection using an no-arg contructor; then registered with the server # or will be instantiated via reflection using a no-arg constructor; then registered with the server
#custom-provider-classes: #custom-provider-classes:
# specify what should be stored in meta.source based on StoreMetaSourceInformationEnum defaults to NONE # specify what should be stored in meta.source based on StoreMetaSourceInformationEnum defaults to NONE
# store_meta_source_information: NONE # store_meta_source_information: NONE

View File

@@ -3,7 +3,7 @@ server:
# servlet: # servlet:
# context-path: /example/path # context-path: /example/path
port: 8080 port: 8080
#Adds the option to go to eg. http://localhost:8080/actuator/health for seeing the running configuration #Adds the option to go to e.g. http://localhost:8080/actuator/health for seeing the running configuration
#see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints #see https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.endpoints
management: management:
#The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default. #The following configuration will enable the actuator endpoints at /actuator/health, /actuator/info, /actuator/prometheus, /actuator/metrics. For security purposes, only /actuator/health is enabled by default.
@@ -11,7 +11,7 @@ management:
enabled-by-default: false enabled-by-default: false
web: web:
exposure: exposure:
include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all' include: 'health' # or e.g. 'info,health,prometheus,metrics' or '*' for all
endpoint: endpoint:
info: info:
enabled: true enabled: true
@@ -87,6 +87,11 @@ hapi:
caregaps: caregaps:
reporter: "default" reporter: "default"
section_author: "default" section_author: "default"
terminologyServerClientSettings:
maxRetryCount: 3
retryIntervalMillis: 1000
timeoutSeconds: 30
socketTimeout: 60
cql: cql:
use_embedded_libraries: true use_embedded_libraries: true
compiler: compiler:
@@ -144,7 +149,7 @@ hapi:
### forces the use of the https:// protocol for the returned server address. ### forces the use of the https:// protocol for the returned server address.
### alternatively, it may be set using the X-Forwarded-Proto header. ### alternatively, it may be set using the X-Forwarded-Proto header.
# use_apache_address_strategy_https: false # use_apache_address_strategy_https: false
### enables the server to overwrite defaults on HTML, css, etc. under the url pattern of eg. /content/custom ** ### enables the server to overwrite defaults on HTML, css, etc. under the url pattern of e.g. /content/custom **
### Folder with custom content MUST be named custom. If omitted then default content applies ### Folder with custom content MUST be named custom. If omitted then default content applies
#custom_content_path: ./custom #custom_content_path: ./custom
### enables the server host custom content. If e.g. the value ./configs/app is supplied then the content ### enables the server host custom content. If e.g. the value ./configs/app is supplied then the content
@@ -193,7 +198,7 @@ hapi:
# enable_index_of_type: true # enable_index_of_type: true
# enable_index_contained_resource: false # enable_index_contained_resource: false
# resource_dbhistory_enabled: false # resource_dbhistory_enabled: false
### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! ### !!Extended Lucene/Elasticsearch Indexing is still an experimental feature, expect some features (e.g. _total=accurate) to not work as expected!!
### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html
advanced_lucene_indexing: false advanced_lucene_indexing: false
search_index_full_text_enabled: false search_index_full_text_enabled: false
@@ -252,12 +257,12 @@ hapi:
# comma-separated list of fully qualified interceptor classes. # comma-separated list of fully qualified interceptor classes.
# classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages',
# or will be instantiated via reflection using an no-arg contructor; then registered with the server # or will be instantiated via reflection using a no-arg constructor; then registered with the server
#custom-interceptor-classes: #custom-interceptor-classes:
# comma-separated list of fully qualified provider classes. # comma-separated list of fully qualified provider classes.
# classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages', # classes listed here will be fetched from the Spring context when combined with 'custom-bean-packages',
# or will be instantiated via reflection using an no-arg contructor; then registered with the server # or will be instantiated via reflection using a no-arg constructor; then registered with the server
#custom-provider-classes: #custom-provider-classes:
# Threadpool size for BATCH'ed GETs in a bundle. # Threadpool size for BATCH'ed GETs in a bundle.

View File

@@ -47,7 +47,7 @@
<p> <p>
This UI can be customized! This UI can be customized!
You might want to put rules on who can use this server here, or You might want to put rules on who can use this server here, or
notices about privacy, or whatever else you want.. notices about privacy, or whatever else you want.
</p> </p>
</div> </div>
</div> </div>
@@ -63,7 +63,7 @@
<script> <script>
// Function to check if a file exists using AJAX // Function to check if a file exists using AJAX
function fileExists(filePath, callback) { function fileExists(filePath, callback) {
var xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
callback(xhr.status === 200); callback(xhr.status === 200);
@@ -77,7 +77,7 @@
fileExists("content/custom/about.html", function(exists) { fileExists("content/custom/about.html", function(exists) {
if (exists) { if (exists) {
loadFile("content/custom/about.html", function(content) { loadFile("content/custom/about.html", function(content) {
var replacementContainer = document.getElementById("replacementAbout"); let replacementContainer = document.getElementById("replacementAbout");
replacementContainer.innerHTML = content; replacementContainer.innerHTML = content;
}); });
} }
@@ -85,7 +85,7 @@
// Function to load file content using AJAX // Function to load file content using AJAX
function loadFile(filePath, callback) { function loadFile(filePath, callback) {
var xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) { if (xhr.readyState === 4 && xhr.status === 200) {
callback(xhr.responseText); callback(xhr.responseText);

View File

@@ -80,7 +80,7 @@ class CdsHooksServletIT implements IServerSupport {
ourCdsBase = "http://localhost:" + port + "/cds-services"; ourCdsBase = "http://localhost:" + port + "/cds-services";
var cdsServicesJson = myCdsServiceRegistry.getCdsServicesJson(); var cdsServicesJson = myCdsServiceRegistry.getCdsServicesJson();
if (cdsServicesJson != null && cdsServicesJson.getServices() != null && !cdsServicesJson.getServices().isEmpty()) { if (cdsServicesJson != null && cdsServicesJson.getServices() != null) {
var services = cdsServicesJson.getServices(); var services = cdsServicesJson.getServices();
for (int i = 0; i < services.size(); i++) { for (int i = 0; i < services.size(); i++) {
myCdsServiceRegistry.unregisterService(services.get(i).getId(), "CR"); myCdsServiceRegistry.unregisterService(services.get(i).getId(), "CR");
@@ -91,12 +91,12 @@ class CdsHooksServletIT implements IServerSupport {
private Boolean hasCdsServices() throws IOException { private Boolean hasCdsServices() throws IOException {
var response = callCdsServicesDiscovery(); var response = callCdsServicesDiscovery();
// NOTE: this is looking for a repsonse that indicates there are CDS services availalble. // NOTE: this is looking for a response that indicates there are CDS services available.
// And empty response looks like: {"services": []} // And empty response looks like: {"services": []}
// Looking at the actual response string consumes the InputStream which has side-effects, making it tricky to compare the actual contents. // Looking at the actual response string consumes the InputStream which has side effects, making it tricky to compare the actual contents.
// Hence the test just looks at the length to make this determination. // Hence, the test just looks at the length to make this determination.
// The actual response has newlines in it which vary in size on some systems, but a value of 25 seems to work across linux/mac/windows // The actual response has newlines in it which vary in size on some systems, but a value of 25 seems to work across linux/mac/windows
// to ensure the repsonse actually contains CDS services in it // to ensure the response actually contains CDS services in it
return response.getEntity().getContentLength() > 25 || response.getEntity().isChunked(); return response.getEntity().getContentLength() > 25 || response.getEntity().isChunked();
} }
@@ -120,23 +120,24 @@ class CdsHooksServletIT implements IServerSupport {
@Test @Test
void testCdsHooks() throws IOException { void testCdsHooks() throws IOException {
loadBundle("r4/HelloWorld-Bundle.json", ourCtx, ourClient); loadBundle("r4/HelloWorld-Bundle.json", ourCtx, ourClient);
await().atMost(10000, TimeUnit.MILLISECONDS).until(() -> hasCdsServices()); await().atMost(10000, TimeUnit.MILLISECONDS).until(this::hasCdsServices);
var cdsRequest = "{\n" + var cdsRequest = """
" \"hookInstance\": \"12345\",\n" + {
" \"hook\": \"patient-view\",\n" + "hookInstance": "12345",
" \"context\": {\n" + "hook": "patient-view",
" \"userId\": \"Practitioner/example\",\n" + "context": {
" \"patientId\": \"Patient/example-hello-world\"\n" + "userId": "Practitioner/example",
" },\n" + "patientId": "Patient/example-hello-world"
" \"prefetch\": {\n" + },
" \"item1\": {\n" + "prefetch": {
" \"resourceType\": \"Patient\",\n" + "item1": {
" \"id\": \"example-hello-world\",\n" + "resourceType": "Patient",
" \"gender\": \"male\",\n" + "id": "example-hello-world",
" \"birthDate\": \"2000-01-01\"\n" + "gender": "male",
" }\n" + "birthDate": "2000-01-01"
" }\n" + }
"}"; }
}""";
try (CloseableHttpClient httpClient = HttpClients.createDefault()) { try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
HttpPost request = new HttpPost(ourCdsBase + "/hello-world"); HttpPost request = new HttpPost(ourCdsBase + "/hello-world");
request.setEntity(new StringEntity(cdsRequest)); request.setEntity(new StringEntity(cdsRequest));
@@ -158,7 +159,7 @@ class CdsHooksServletIT implements IServerSupport {
@Test @Test
void testRec10() throws IOException { void testRec10() throws IOException {
loadBundle("r4/opioidcds-10-order-sign-bundle.json", ourCtx, ourClient); loadBundle("r4/opioidcds-10-order-sign-bundle.json", ourCtx, ourClient);
await().atMost(20000, TimeUnit.MILLISECONDS).until(() -> hasCdsServices()); await().atMost(20000, TimeUnit.MILLISECONDS).until(this::hasCdsServices);
var fhirServer = " \"fhirServer\": " + "\"" + ourServerBase + "\"" + ",\n"; var fhirServer = " \"fhirServer\": " + "\"" + ourServerBase + "\"" + ",\n";
var cdsRequest = "{\n" + var cdsRequest = "{\n" +
" \"hookInstance\": \"055b009c-4a7d-4db4-a35e-0e5198918ed1\",\n" + " \"hookInstance\": \"055b009c-4a7d-4db4-a35e-0e5198918ed1\",\n" +

View File

@@ -13,13 +13,10 @@ import java.io.IOException;
import java.util.Date; import java.util.Date;
import java.util.GregorianCalendar; import java.util.GregorianCalendar;
import java.util.List;
import co.elastic.clients.elasticsearch.ElasticsearchClient; import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.indices.IndexSettings; import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.json.JsonData;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import org.elasticsearch.client.RequestOptions;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.DateTimeType; import org.hl7.fhir.r4.model.DateTimeType;
@@ -68,13 +65,12 @@ import org.testcontainers.junit.jupiter.Testcontainers;
"spring.jpa.properties.hibernate.search.backend.analysis.configurer=ca.uhn.fhir.jpa.search.elastic.HapiElasticsearchAnalysisConfigurer" "spring.jpa.properties.hibernate.search.backend.analysis.configurer=ca.uhn.fhir.jpa.search.elastic.HapiElasticsearchAnalysisConfigurer"
}) })
@ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class) @ContextConfiguration(initializers = ElasticsearchLastNR4IT.Initializer.class)
public class ElasticsearchLastNR4IT { class ElasticsearchLastNR4IT {
private IGenericClient ourClient;
private IGenericClient ourClient;
private FhirContext ourCtx; private FhirContext ourCtx;
@Container @Container
public static ElasticsearchContainer embeddedElastic = TestElasticsearchContainerHelper.getEmbeddedElasticSearch(); public static ElasticsearchContainer embeddedElastic = TestElasticsearchContainerHelper.getEmbeddedElasticSearch();
@Autowired @Autowired
private ElasticsearchSvcImpl myElasticsearchSvc; private ElasticsearchSvcImpl myElasticsearchSvc;
@@ -109,7 +105,7 @@ public class ElasticsearchLastNR4IT {
//@Test //@Test
void testLastN() throws IOException, InterruptedException { void testLastN() throws IOException, InterruptedException {
Thread.sleep(2000); Thread.sleep(2000);
Patient pt = new Patient(); Patient pt = new Patient();
pt.addName().setFamily("Lastn").addGiven("Arthur"); pt.addName().setFamily("Lastn").addGiven("Arthur");

View File

@@ -33,7 +33,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
"hapi.fhir.partitioning.database_partition_mode_enabled=true", "hapi.fhir.partitioning.database_partition_mode_enabled=true",
"hapi.fhir.partitioning.patient_id_partitioning_mode=true" "hapi.fhir.partitioning.patient_id_partitioning_mode=true"
}) })
public class ExampleServerDbpmR5IT { class ExampleServerDbpmR5IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
private IGenericClient ourClient; private IGenericClient ourClient;

View File

@@ -51,8 +51,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
"hapi.fhir.allow_placeholder_references=true", "hapi.fhir.allow_placeholder_references=true",
"spring.main.allow-bean-definition-overriding=true" "spring.main.allow-bean-definition-overriding=true"
}) })
class ExampleServerDstu3IT implements IServerSupport { class ExampleServerDstu3IT implements IServerSupport {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu3IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu3IT.class);

View File

@@ -39,7 +39,7 @@ class ExampleServerR4BIT {
@Order(0) @Order(0)
void testCreateAndRead() { void testCreateAndRead() {
String methodName = "testCreateAndRead"; String methodName = "testCreateAndRead";
ourLog.info("Entering " + methodName + "()..."); ourLog.info("Entering {}()...", methodName);
Patient pt = new Patient(); Patient pt = new Patient();
pt.setActive(true); pt.setActive(true);
@@ -56,51 +56,52 @@ class ExampleServerR4BIT {
@Test @Test
void testBatchPutWithIdenticalTags() { void testBatchPutWithIdenticalTags() {
String batchPuts = "{\n" + String batchPuts = """
"\t\"resourceType\": \"Bundle\",\n" + {
"\t\"id\": \"patients\",\n" + \t"resourceType": "Bundle",
"\t\"type\": \"batch\",\n" + \t"id": "patients",
"\t\"entry\": [\n" + \t"type": "batch",
"\t\t{\n" + \t"entry": [
"\t\t\t\"request\": {\n" + \t\t{
"\t\t\t\t\"method\": \"PUT\",\n" + \t\t\t"request": {
"\t\t\t\t\"url\": \"Patient/pat-1\"\n" + \t\t\t\t"method": "PUT",
"\t\t\t},\n" + \t\t\t\t"url": "Patient/pat-1"
"\t\t\t\"resource\": {\n" + \t\t\t},
"\t\t\t\t\"resourceType\": \"Patient\",\n" + \t\t\t"resource": {
"\t\t\t\t\"id\": \"pat-1\",\n" + \t\t\t\t"resourceType": "Patient",
"\t\t\t\t\"meta\": {\n" + \t\t\t\t"id": "pat-1",
"\t\t\t\t\t\"tag\": [\n" + \t\t\t\t"meta": {
"\t\t\t\t\t\t{\n" + \t\t\t\t\t"tag": [
"\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + \t\t\t\t\t\t{
"\t\t\t\t\t\t\t\"code\": \"value2\"\n" + \t\t\t\t\t\t\t"system": "http://mysystem.org",
"\t\t\t\t\t\t}\n" + \t\t\t\t\t\t\t"code": "value2"
"\t\t\t\t\t]\n" + \t\t\t\t\t\t}
"\t\t\t\t}\n" + \t\t\t\t\t]
"\t\t\t},\n" + \t\t\t\t}
"\t\t\t\"fullUrl\": \"/Patient/pat-1\"\n" + \t\t\t},
"\t\t},\n" + \t\t\t"fullUrl": "/Patient/pat-1"
"\t\t{\n" + \t\t},
"\t\t\t\"request\": {\n" + \t\t{
"\t\t\t\t\"method\": \"PUT\",\n" + \t\t\t"request": {
"\t\t\t\t\"url\": \"Patient/pat-2\"\n" + \t\t\t\t"method": "PUT",
"\t\t\t},\n" + \t\t\t\t"url": "Patient/pat-2"
"\t\t\t\"resource\": {\n" + \t\t\t},
"\t\t\t\t\"resourceType\": \"Patient\",\n" + \t\t\t"resource": {
"\t\t\t\t\"id\": \"pat-2\",\n" + \t\t\t\t"resourceType": "Patient",
"\t\t\t\t\"meta\": {\n" + \t\t\t\t"id": "pat-2",
"\t\t\t\t\t\"tag\": [\n" + \t\t\t\t"meta": {
"\t\t\t\t\t\t{\n" + \t\t\t\t\t"tag": [
"\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + \t\t\t\t\t\t{
"\t\t\t\t\t\t\t\"code\": \"value2\"\n" + \t\t\t\t\t\t\t"system": "http://mysystem.org",
"\t\t\t\t\t\t}\n" + \t\t\t\t\t\t\t"code": "value2"
"\t\t\t\t\t]\n" + \t\t\t\t\t\t}
"\t\t\t\t}\n" + \t\t\t\t\t]
"\t\t\t},\n" + \t\t\t\t}
"\t\t\t\"fullUrl\": \"/Patient/pat-2\"\n" + \t\t\t},
"\t\t}\n" + \t\t\t"fullUrl": "/Patient/pat-2"
"\t]\n" + \t\t}
"}"; \t]
}""";
Bundle bundle = FhirContext.forR4B().newJsonParser().parseResource(Bundle.class, batchPuts); Bundle bundle = FhirContext.forR4B().newJsonParser().parseResource(Bundle.class, batchPuts);
ourClient.transaction().withBundle(bundle).execute(); ourClient.transaction().withBundle(bundle).execute();
} }

View File

@@ -18,7 +18,6 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.*; import org.hl7.fhir.r4.model.*;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@@ -34,7 +33,6 @@ import org.springframework.boot.test.web.server.LocalServerPort;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -44,6 +42,7 @@ import static org.hamcrest.Matchers.equalTo;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters; import static org.opencds.cqf.fhir.utility.r4.Parameters.parameters;
import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart; import static org.opencds.cqf.fhir.utility.r4.Parameters.stringPart;
@@ -92,7 +91,7 @@ class ExampleServerR4IT implements IServerSupport {
@Order(0) @Order(0)
void testCreateAndRead() { void testCreateAndRead() {
String methodName = "testCreateAndRead"; String methodName = "testCreateAndRead";
ourLog.info("Entering " + methodName + "()..."); ourLog.info("Entering {}()...", methodName);
Patient pt = new Patient(); Patient pt = new Patient();
pt.setActive(true); pt.setActive(true);
@@ -132,7 +131,7 @@ class ExampleServerR4IT implements IServerSupport {
List<Parameters.ParametersParameterComponent> response = outParams.getParameter(); List<Parameters.ParametersParameterComponent> response = outParams.getParameter();
assertFalse(response.isEmpty()); assertFalse(response.isEmpty());
Parameters.ParametersParameterComponent component = response.get(0); Parameters.ParametersParameterComponent component = response.get(0);
assertTrue(component.getResource() instanceof MeasureReport); assertInstanceOf(MeasureReport.class, component.getResource());
MeasureReport report = (MeasureReport) component.getResource(); MeasureReport report = (MeasureReport) component.getResource();
assertEquals(measureUrl + "|0.0.003", report.getMeasure()); assertEquals(measureUrl + "|0.0.003", report.getMeasure());
} }
@@ -150,66 +149,57 @@ class ExampleServerR4IT implements IServerSupport {
void testSimpleDateCqlExecutionProvider() { void testSimpleDateCqlExecutionProvider() {
Parameters params = parameters(stringPart("expression", "Interval[Today() - 2 years, Today())")); Parameters params = parameters(stringPart("expression", "Interval[Today() - 2 years, Today())"));
Parameters results = runCqlExecution(params); Parameters results = runCqlExecution(params);
assertTrue(results.getParameter("return").getValue() instanceof Period); assertInstanceOf(Period.class, results.getParameter("return").getValue());
}
private IBaseResource loadRec(String theLocation, FhirContext theCtx, IGenericClient theClient) throws IOException {
String json = stringFromResource(theLocation);
List<IBaseResource> resList = new ArrayList<>();
IBaseResource resource = (IBaseResource) theCtx.newJsonParser().parseResource(json);
resList.add(resource);
var result = theClient.transaction().withResources(resList).execute();
//.withResources(resource).execute();
return result.get(0);
} }
@Test @Test
void testBatchPutWithIdenticalTags() { void testBatchPutWithIdenticalTags() {
String batchPuts = "{\n" + String batchPuts = """
"\t\"resourceType\": \"Bundle\",\n" + {
"\t\"id\": \"patients\",\n" + \t"resourceType": "Bundle",
"\t\"type\": \"batch\",\n" + \t"id": "patients",
"\t\"entry\": [\n" + \t"type": "batch",
"\t\t{\n" + \t"entry": [
"\t\t\t\"request\": {\n" + \t\t{
"\t\t\t\t\"method\": \"PUT\",\n" + \t\t\t"request": {
"\t\t\t\t\"url\": \"Patient/pat-1\"\n" + \t\t\t\t"method": "PUT",
"\t\t\t},\n" + \t\t\t\t"url": "Patient/pat-1"
"\t\t\t\"resource\": {\n" + \t\t\t},
"\t\t\t\t\"resourceType\": \"Patient\",\n" + \t\t\t"resource": {
"\t\t\t\t\"id\": \"pat-1\",\n" + \t\t\t\t"resourceType": "Patient",
"\t\t\t\t\"meta\": {\n" + \t\t\t\t"id": "pat-1",
"\t\t\t\t\t\"tag\": [\n" + \t\t\t\t"meta": {
"\t\t\t\t\t\t{\n" + \t\t\t\t\t"tag": [
"\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + \t\t\t\t\t\t{
"\t\t\t\t\t\t\t\"code\": \"value2\"\n" + \t\t\t\t\t\t\t"system": "http://mysystem.org",
"\t\t\t\t\t\t}\n" + \t\t\t\t\t\t\t"code": "value2"
"\t\t\t\t\t]\n" + \t\t\t\t\t\t}
"\t\t\t\t}\n" + \t\t\t\t\t]
"\t\t\t},\n" + \t\t\t\t}
"\t\t\t\"fullUrl\": \"/Patient/pat-1\"\n" + \t\t\t},
"\t\t},\n" + \t\t\t"fullUrl": "/Patient/pat-1"
"\t\t{\n" + \t\t},
"\t\t\t\"request\": {\n" + \t\t{
"\t\t\t\t\"method\": \"PUT\",\n" + \t\t\t"request": {
"\t\t\t\t\"url\": \"Patient/pat-2\"\n" + \t\t\t\t"method": "PUT",
"\t\t\t},\n" + \t\t\t\t"url": "Patient/pat-2"
"\t\t\t\"resource\": {\n" + \t\t\t},
"\t\t\t\t\"resourceType\": \"Patient\",\n" + \t\t\t"resource": {
"\t\t\t\t\"id\": \"pat-2\",\n" + \t\t\t\t"resourceType": "Patient",
"\t\t\t\t\"meta\": {\n" + \t\t\t\t"id": "pat-2",
"\t\t\t\t\t\"tag\": [\n" + \t\t\t\t"meta": {
"\t\t\t\t\t\t{\n" + \t\t\t\t\t"tag": [
"\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + \t\t\t\t\t\t{
"\t\t\t\t\t\t\t\"code\": \"value2\"\n" + \t\t\t\t\t\t\t"system": "http://mysystem.org",
"\t\t\t\t\t\t}\n" + \t\t\t\t\t\t\t"code": "value2"
"\t\t\t\t\t]\n" + \t\t\t\t\t\t}
"\t\t\t\t}\n" + \t\t\t\t\t]
"\t\t\t},\n" + \t\t\t\t}
"\t\t\t\"fullUrl\": \"/Patient/pat-2\"\n" + \t\t\t},
"\t\t}\n" + \t\t\t"fullUrl": "/Patient/pat-2"
"\t]\n" + \t\t}
"}"; \t]
}""";
Bundle bundle = FhirContext.forR4().newJsonParser().parseResource(Bundle.class, batchPuts); Bundle bundle = FhirContext.forR4().newJsonParser().parseResource(Bundle.class, batchPuts);
ourClient.transaction().withBundle(bundle).execute(); ourClient.transaction().withBundle(bundle).execute();
} }
@@ -277,8 +267,8 @@ class ExampleServerR4IT implements IServerSupport {
var reporter = crProperties.getCareGaps().getReporter(); var reporter = crProperties.getCareGaps().getReporter();
var author = crProperties.getCareGaps().getSection_author(); var author = crProperties.getCareGaps().getSection_author();
assertTrue(reporter.equals("Organization/alphora")); assertEquals("Organization/alphora", reporter);
assertTrue(author.equals("Organization/alphora-author")); assertEquals("Organization/alphora-author", author);
String periodStartValid = "2019-01-01"; String periodStartValid = "2019-01-01";
String periodEndValid = "2019-12-31"; String periodEndValid = "2019-12-31";
@@ -327,7 +317,7 @@ class ExampleServerR4IT implements IServerSupport {
@Test @Test
void testDiffOperationIsRegistered() { void testDiffOperationIsRegistered() {
String methodName = "testDiff"; String methodName = "testDiff";
ourLog.info("Entering " + methodName + "()..."); ourLog.info("Entering {}()...", methodName);
Patient pt = new Patient(); Patient pt = new Patient();
pt.setActive(true); pt.setActive(true);

View File

@@ -4,7 +4,6 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -15,6 +14,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public interface IServerSupport { public interface IServerSupport {
@@ -45,6 +45,6 @@ public interface IServerSupport {
Resource resource = resourceLoader.getResource(theLocation); Resource resource = resourceLoader.getResource(theLocation);
is = resource.getInputStream(); is = resource.getInputStream();
} }
return IOUtils.toString(is, Charsets.UTF_8); return IOUtils.toString(is, StandardCharsets.UTF_8);
} }
} }

View File

@@ -1,13 +1,9 @@
package ca.uhn.fhir.jpa.starter; package ca.uhn.fhir.jpa.starter;
import org.opencds.cqf.fhir.cql.EvaluationSettings;
import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions;
import org.opencds.cqf.fhir.utility.ValidationProfile;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration @Configuration
public class MeasureEvaluationConfig { public class MeasureEvaluationConfig {

View File

@@ -17,12 +17,10 @@ import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
@@ -100,45 +98,42 @@ public class ParallelUpdatesVersionConflictTest {
List<Callable<Integer>> callables = new ArrayList<>(); List<Callable<Integer>> callables = new ArrayList<>();
for (int i = 0; i < threadCnt; i++) { for (int i = 0; i < threadCnt; i++) {
final int cnt = i; final int cnt = i;
Callable<Integer> callable = new Callable<>() { Callable<Integer> callable = () -> {
@Override Patient pat = new Patient();
public Integer call() throws Exception { //make sure to change something so the server doesn't short circuit on a no-op
Patient pat = new Patient(); pat.addName().setFamily("fam-" + cnt);
//make sure to change something so the server doesnt short circuit on a no-op pat.setId(patientId);
pat.addName().setFamily("fam-" + cnt);
pat.setId(patientId);
if( useBundles) { if( useBundles) {
Bundle b = new Bundle(); Bundle b = new Bundle();
b.setType(BundleType.TRANSACTION); b.setType(BundleType.TRANSACTION);
BundleEntryComponent bec = b.addEntry(); BundleEntryComponent bec = b.addEntry();
bec.setResource(pat); bec.setResource(pat);
//bec.setFullUrl("Patient/" + patId); //bec.setFullUrl("Patient/" + patId);
Bundle.BundleEntryRequestComponent req = bec.getRequest(); Bundle.BundleEntryRequestComponent req = bec.getRequest();
req.setUrl("Patient/" + patientId); req.setUrl("Patient/" + patientId);
req.setMethod(HTTPVerb.PUT); req.setMethod(HTTPVerb.PUT);
bec.setRequest(req); bec.setRequest(req);
Bundle returnBundle = client.transaction().withBundle(b) Bundle returnBundle = client.transaction().withBundle(b)
.withAdditionalHeader(headerName, "retry; max-retries=10") .withAdditionalHeader(headerName, "retry; max-retries=10")
.execute(); .execute();
String statusString = returnBundle.getEntryFirstRep().getResponse().getStatus(); String statusString = returnBundle.getEntryFirstRep().getResponse().getStatus();
ourLog.trace("statusString->{}", statusString); ourLog.trace("statusString->{}", statusString);
try { try {
return Integer.parseInt(statusString.substring(0,3)); return Integer.parseInt(statusString.substring(0,3));
}catch(NumberFormatException nfe) { }catch(NumberFormatException nfe) {
return 500; return 500;
}
}
else {
MethodOutcome outcome = client.update().resource(pat).withId(patientId)
.withAdditionalHeader(headerName, "retry; max-retries=10")
.execute();
ourLog.trace("updated patient: " + outcome.getResponseStatusCode());
return outcome.getResponseStatusCode();
} }
} }
else {
MethodOutcome outcome = client.update().resource(pat).withId(patientId)
.withAdditionalHeader(headerName, "retry; max-retries=10")
.execute();
ourLog.trace("updated patient: {}", outcome.getResponseStatusCode());
return outcome.getResponseStatusCode();
}
}; };
callables.add(callable); callables.add(callable);
} }

View File

@@ -43,7 +43,7 @@ public class SocketImplementation {
/** /**
* This method is executed when the client is connecting to the server. * This method is executed when the client is connecting to the server.
* In this case, we are sending a message to create the subscription dynamiclly * In this case, we are sending a message to create the subscription dynamically
* *
* @param session * @param session
*/ */
@@ -58,7 +58,6 @@ public class SocketImplementation {
ourLog.info("Connection: DONE"); ourLog.info("Connection: DONE");
} catch (Throwable t) { } catch (Throwable t) {
t.printStackTrace();
ourLog.error("Failure", t); ourLog.error("Failure", t);
} }
} }
@@ -70,7 +69,7 @@ public class SocketImplementation {
*/ */
@OnMessage @OnMessage
public void onMessage(String theMsg) { public void onMessage(String theMsg) {
ourLog.info("Got msg: " + theMsg); ourLog.info("Got msg: {}", theMsg);
myMessages.add(theMsg); myMessages.add(theMsg);
if (theMsg.startsWith("bound ")) { if (theMsg.startsWith("bound ")) {
@@ -78,7 +77,7 @@ public class SocketImplementation {
mySubsId = (theMsg.substring("bound ".length())); mySubsId = (theMsg.substring("bound ".length()));
} else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) { } else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) {
String text = theMsg.substring(("add " + mySubsId + "\n").length()); String text = theMsg.substring(("add " + mySubsId + "\n").length());
ourLog.info("text: " + text); ourLog.info("text: {}", text);
} else if (theMsg.startsWith("ping ")) { } else if (theMsg.startsWith("ping ")) {
myPingCount++; myPingCount++;
} else { } else {

View File

@@ -21,8 +21,7 @@ public class CustomInterceptorBean {
IBaseResource resource = servletRequestDetails.getResource(); IBaseResource resource = servletRequestDetails.getResource();
// add an extension before saving the resource to mark it // add an extension before saving the resource to mark it
if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient) { if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient pat) {
Patient pat = (Patient) resource;
Extension ext = pat.addExtension(); Extension ext = pat.addExtension();
ext.setUrl("http://some.custom.pkg1/CustomInterceptorBean"); ext.setUrl("http://some.custom.pkg1/CustomInterceptorBean");
ext.setValue(new StringType("CustomInterceptorBean wuz here")); ext.setValue(new StringType("CustomInterceptorBean wuz here"));

View File

@@ -16,8 +16,7 @@ public class CustomInterceptorPojo {
IBaseResource resource = servletRequestDetails.getResource(); IBaseResource resource = servletRequestDetails.getResource();
// add an extension before saving the resource to mark it // add an extension before saving the resource to mark it
if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient) { if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient pat) {
Patient pat = (Patient) resource;
Extension ext = pat.addExtension(); Extension ext = pat.addExtension();
ext.setUrl("http://some.custom.pkg1/CustomInterceptorPojo"); ext.setUrl("http://some.custom.pkg1/CustomInterceptorPojo");
ext.setValue(new StringType("CustomInterceptorPojo wuz here")); ext.setValue(new StringType("CustomInterceptorPojo wuz here"));

View File

@@ -4,7 +4,7 @@ management:
enabled-by-default: false enabled-by-default: false
web: web:
exposure: exposure:
include: 'info,health,prometheus,metrics' # or '*' for all' include: 'info,health,prometheus,metrics' # or '*' for all
endpoint: endpoint:
info: info:
enabled: true enabled: true
@@ -31,7 +31,7 @@ spring:
allow-bean-definition-overriding: true allow-bean-definition-overriding: true
flyway: flyway:
enabled: false enabled: false
check-location: false fail-on-missing-locations: false
baselineOnMigrate: true baselineOnMigrate: true
datasource: datasource:
url: jdbc:h2:mem:test_mem url: jdbc:h2:mem:test_mem
@@ -126,7 +126,7 @@ hapi:
# enable_index_missing_fields: false # enable_index_missing_fields: false
# enable_index_of_type: true # enable_index_of_type: true
# enable_index_contained_resource: false # enable_index_contained_resource: false
### !!Extended Lucene/Elasticsearch Indexing is still a experimental feature, expect some features (e.g. _total=accurate) to not work as expected!! ### !!Extended Lucene/Elasticsearch Indexing is still an experimental feature, expect some features (e.g. _total=accurate) to not work as expected!!
### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html ### more information here: https://hapifhir.io/hapi-fhir/docs/server_jpa/elastic.html
advanced_lucene_indexing: false advanced_lucene_indexing: false
search_index_full_text_enabled: false search_index_full_text_enabled: false

View File

@@ -15,7 +15,7 @@ on the jetbrains website, in addition to the [API reference][Link-HTTP-Client-Re
### Formatting ### Formatting
Each test file corresponds to a given section within the hapifhir documentation as close as possible. For Each test file corresponds to a given section within the hapifhir documentation as close as possible. For
example, there is a `plain_server.rest` file, which attemps to smoke test all basic functionality outlined in the section example, there is a `plain_server.rest` file, which attempts to smoke test all basic functionality outlined in the section
[within the docs][Link-HAPI-FHIR-docs-plain-server]. [within the docs][Link-HAPI-FHIR-docs-plain-server].
Individual tests are formatted as follows: Individual tests are formatted as follows:
@@ -38,7 +38,7 @@ To run these tests against a specific server, configure the server details withi
``` ```
### Running the Tests ### Running the Tests
Within IntelliJ, right click the file you wish to run tests in and select the `Run All` option from the menu. Within IntelliJ, right-click the file you wish to run tests in and select the `Run All` option from the menu.
**Important:** Tests may not work individually when run. Often times, tests need to be run sequentially, as they depend **Important:** Tests may not work individually when run. Often times, tests need to be run sequentially, as they depend
on resources/references from previous tests to complete. _(An example of this would be adding a Patient, saving the id, on resources/references from previous tests to complete. _(An example of this would be adding a Patient, saving the id,

View File

@@ -81,13 +81,13 @@ Content-Type: application/json
client.assert(resourceType === "Bundle", "Expected 'Bundle' but received '" + resourceType + "'"); client.assert(resourceType === "Bundle", "Expected 'Bundle' but received '" + resourceType + "'");
}); });
client.test("All patient additions successful", function() { client.test("All patient additions successful", function() {
for (var index = 0; index < response.body.entry.length; index++) { for (let index = 0; index < response.body.entry.length; index++) {
client.assert(response.body.entry[index].response.status === "201 Created", "Expected '201 Created' for patient index " + index + " but received '" + response.body.entry[index].response.status + "'"); client.assert(response.body.entry[index].response.status === "201 Created", "Expected '201 Created' for patient index " + index + " but received '" + response.body.entry[index].response.status + "'");
} }
}); });
const batch_patient_location = response.body.entry[0].response.location; const batch_patient_location = response.body.entry[0].response.location;
const indexOfHistory = batch_patient_location.lastIndexOf("/_history"); const indexOfHistory = batch_patient_location.lastIndexOf("/_history");
var trimmed_location = batch_patient_location.substring(0, indexOfHistory); let trimmed_location = batch_patient_location.substring(0, indexOfHistory);
trimmed_location = trimmed_location.replace("Patient/", "") trimmed_location = trimmed_location.replace("Patient/", "")
client.global.set("batch_patient_id", trimmed_location); client.global.set("batch_patient_id", trimmed_location);
%} %}