Merge remote-tracking branch 'origin/master' into rel_8_3_tracking
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -124,7 +124,6 @@ local.properties
|
|||||||
.factorypath
|
.factorypath
|
||||||
.project
|
.project
|
||||||
.settings
|
.settings
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
.sts4-cache
|
||||||
|
|
||||||
# Code Recommenders
|
# Code Recommenders
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# HAPI FHIR JPA Server Starter Helm Chart
|
# HAPI FHIR JPA Server Starter Helm Chart
|
||||||
|
|
||||||
  
|
  
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -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>
|
||||||
|
|||||||
@@ -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/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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" +
|
||||||
|
|||||||
@@ -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,8 +65,7 @@ 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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|
||||||
|
|||||||
@@ -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,11 +98,9 @@ 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
|
|
||||||
public Integer call() throws Exception {
|
|
||||||
Patient pat = new Patient();
|
Patient pat = new Patient();
|
||||||
//make sure to change something so the server doesnt short circuit on a no-op
|
//make sure to change something so the server doesn't short circuit on a no-op
|
||||||
pat.addName().setFamily("fam-" + cnt);
|
pat.addName().setFamily("fam-" + cnt);
|
||||||
pat.setId(patientId);
|
pat.setId(patientId);
|
||||||
|
|
||||||
@@ -135,10 +131,9 @@ public class ParallelUpdatesVersionConflictTest {
|
|||||||
MethodOutcome outcome = client.update().resource(pat).withId(patientId)
|
MethodOutcome outcome = client.update().resource(pat).withId(patientId)
|
||||||
.withAdditionalHeader(headerName, "retry; max-retries=10")
|
.withAdditionalHeader(headerName, "retry; max-retries=10")
|
||||||
.execute();
|
.execute();
|
||||||
ourLog.trace("updated patient: " + outcome.getResponseStatusCode());
|
ourLog.trace("updated patient: {}", outcome.getResponseStatusCode());
|
||||||
return outcome.getResponseStatusCode();
|
return outcome.getResponseStatusCode();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
callables.add(callable);
|
callables.add(callable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
%}
|
%}
|
||||||
|
|||||||
Reference in New Issue
Block a user