Merge pull request #1 from hapifhir/master

update from base repo
This commit is contained in:
winne42
2020-09-23 11:47:02 +02:00
committed by GitHub
31 changed files with 2006 additions and 1665 deletions

8
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"files.exclude": {
"**/.classpath": true,
"**/.project": true,
"**/.settings": true,
"**/.factorypath": true
}
}

View File

@@ -2,12 +2,12 @@ FROM maven:3.6.3-jdk-11-slim as build-hapi
WORKDIR /tmp/hapi-fhir-jpaserver-starter WORKDIR /tmp/hapi-fhir-jpaserver-starter
COPY pom.xml . COPY pom.xml .
RUN mvn dependency:go-offline RUN mvn -ntp dependency:go-offline
COPY src/ /tmp/hapi-fhir-jpaserver-starter/src/ COPY src/ /tmp/hapi-fhir-jpaserver-starter/src/
RUN mvn clean install -DskipTests RUN mvn clean install -DskipTests
FROM tomcat:9.0.37-jdk11-openjdk-slim-buster FROM tomcat:9.0.38-jdk11-openjdk-slim-buster
RUN mkdir -p /data/hapi/lucenefiles && chmod 775 /data/hapi/lucenefiles RUN mkdir -p /data/hapi/lucenefiles && chmod 775 /data/hapi/lucenefiles
COPY --from=build-hapi /tmp/hapi-fhir-jpaserver-starter/target/*.war /usr/local/tomcat/webapps/ COPY --from=build-hapi /tmp/hapi-fhir-jpaserver-starter/target/*.war /usr/local/tomcat/webapps/

180
README.md
View File

@@ -4,13 +4,20 @@ This project is a complete starter project you can use to deploy a FHIR server u
Note that this project is specifically intended for end users of the HAPI FHIR JPA server module (in other words, it helps you implement HAPI FHIR, it is not the source of the library itself). If you are looking for the main HAPI FHIR project, see here: https://github.com/jamesagnew/hapi-fhir Note that this project is specifically intended for end users of the HAPI FHIR JPA server module (in other words, it helps you implement HAPI FHIR, it is not the source of the library itself). If you are looking for the main HAPI FHIR project, see here: https://github.com/jamesagnew/hapi-fhir
Need Help? Please see: https://github.com/jamesagnew/hapi-fhir/wiki/Getting-Help
## Prerequisites ## Prerequisites
In order to use this sample, you should have: In order to use this sample, you should have:
- [This project](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) checked out. You may wish to create a GitHub Fork of the project and check that out instead so that you can customize the project and save the results to GitHub. - [This project](https://github.com/hapifhir/hapi-fhir-jpaserver-starter) checked out. You may wish to create a GitHub Fork of the project and check that out instead so that you can customize the project and save the results to GitHub.
- Oracle Java (JDK) installed: Minimum JDK8 or newer.
- Apache Maven build tool (newest version) ### and either
- Oracle Java (JDK) installed: Minimum JDK8 or newer.
- Apache Maven build tool (newest version)
### or
- Docker, as the entire project can be built using multistage docker (with both JDK and maven wrapped in docker) or used directly from [Docker Hub](https://hub.docker.com/repository/docker/hapiproject/hapi)
## Running via [Docker Hub](https://hub.docker.com/repository/docker/hapiproject/hapi) ## Running via [Docker Hub](https://hub.docker.com/repository/docker/hapiproject/hapi)
@@ -18,26 +25,26 @@ Each tagged/released version of `hapi-fhir-jpaserver` is built as a Docker image
``` ```
docker pull hapiproject/hapi:latest docker pull hapiproject/hapi:latest
docker run -p 8080:8080 hapiproject/hapi:tagname docker run -p 8080:8080 -e "spring.batch.job.enabled=false" hapiproject/hapi:tagname
``` ```
This will run the docker image with the default configuration, mapping port 8080 from the container to port 8080 in the host. Once running, you can access `http://localhost:8080/hapi-fhir-jpaserver/fhir` in the browser to access the HAPI FHIR server's UI. This will run the docker image with the default configuration, mapping port 8080 from the container to port 8080 in the host. Once running, you can access `http://localhost:8080/hapi-fhir-jpaserver/` in the browser to access the HAPI FHIR server's UI.
If you change the mapped port, you need to change the configuration used by HAPI to have the correct `server_address` property/value. If you change the mapped port, you need to change the configuration used by HAPI to have the correct `hapi.fhir.tester` property/value.
### Configuration via environment variables ### Configuration via environment variables
You can customize HAPI directly from the `run` command using environment variables. For example: You can customize HAPI directly from the `run` command using environment variables. For example:
`docker run -p 8090:8080 -e server_address=http://localhost:8090/hapi-fhir-jpaserver/fhir hapiproject/hapi:tagname` `docker run -p 8080:8080 -e hapi.fhir.default_encoding=xml hapiproject/hapi:tagname`
HAPI looks in the environment variables for properties in the [hapi.properties](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/hapi.properties) file. HAPI looks in the environment variables for properties in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file for defaults.
### Configuration via overridden hapi.properties file ### Configuration via overridden application.yaml file
You can customize HAPI by telling HAPI to look for the `hapi.properties` file in a different location: You can customize HAPI by telling HAPI to look for the configuration file in a different location, eg.:
`docker run -p 8090:8080 -e hapi.properties=/some/directory/with/hapi.properties hapiproject/hapi:tagname` `docker run -p 8090:8080 -e "--spring.config.location=classpath:/another.application.yaml" hapiproject/hapi:tagname`
### Example docker-compose.yml ### Example docker-compose.yml
@@ -50,11 +57,11 @@ services:
- "8090:8080" - "8090:8080"
configs: configs:
- source: hapi - source: hapi
target: /data/hapi/hapi.properties target: /data/hapi/application.yaml
volumes: volumes:
- hapi-data:/data/hapi - hapi-data:/data/hapi
environment: environment:
JAVA_OPTS: '-Dhapi.properties=/data/hapi/hapi.properties' SPRING_CONFIG_LOCATION: 'file:///data/hapi/application.yaml'
configs: configs:
hapi: hapi:
external: true external: true
@@ -65,15 +72,13 @@ volumes:
## Running locally ## Running locally
The easiest way to run this server is to run it directly in Maven using a built-in Jetty server. To do this, change `src/main/resources/hapi.properties` `server_address` and `server.base` with the values commented out as _For Jetty, use this_ and then execute the following command: The easiest way to run this server entirely depends on your environment requirements. At least, the following 4 ways are supported:
### Using jetty
```bash ```bash
mvn jetty:run mvn jetty:run -Dspring.batch.job.enabled=false
``` ```
Then, browse to the following link to use the server:
[http://localhost:8080/hapi-fhir-jpaserver/](http://localhost:8080/hapi-fhir-jpaserver/)
If you need to run this server on a different port (using Maven), you can change the port in the run command as follows: If you need to run this server on a different port (using Maven), you can change the port in the run command as follows:
@@ -81,43 +86,98 @@ If you need to run this server on a different port (using Maven), you can change
mvn -Djetty.port=8888 jetty:run mvn -Djetty.port=8888 jetty:run
``` ```
And replacing 8888 with the port of your choice. Server will then be accessible at http://localhost:8888/ and eg. http://localhost:8888/fhir/metadata. Remember to adjust you overlay configuration in the application.yaml to eg.
```yaml
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8888/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
```
### Using Spring Boot
```bash
mvn clean package spring-boot:repackage -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 you overlay configuration in the application.yaml to eg.
```yaml
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8080/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
```
### Using Spring Boot and Google distroless
```bash
mvn clean package com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=distroless-hapi && docker run -p 8080:8080 -e spring.batch.job.enabled=false distroless-hapi
```
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 eg.
```yaml
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8080/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
```
### Using the Dockerfile and multistage build
```bash
./build-docker-image.sh && docker run -p 8080:8080 -e "spring.batch.job.enabled=false" 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 you overlay configuration in the application.yaml to eg.
```yaml
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8080/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
```
## Configurations ## Configurations
Much of this HAPI starter project can be configured using the properties file in _src/main/resources/hapi.properties_. By default, this starter project is configured to use Derby as the database. Much of this HAPI starter project can be configured using the yaml file in _src/main/resources/application.yaml_. By default, this starter project is configured to use H2 as the database.
### MySql configuration ### MySql configuration
To configure the starter app to use MySQL, instead of the default Derby, update the hapi.properties file to have the following: To configure the starter app to use MySQL, instead of the default H2, update the application.yaml file to have the following:
- datasource.driver=com.mysql.jdbc.Driver ```yaml
- datasource.url=jdbc:mysql://localhost:3306/hapi_dstu3 spring:
- hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect datasource:
- datasource.username=admin url: 'jdbc:mysql://localhost:3306/hapi_dstu3'
- datasource.password=admin username: admin
password: admin
driverClassName: com.mysql.jdbc.Driver
```
### PostgreSQL configuration ### PostgreSQL configuration
To configure the starter app to use PostgreSQL, instead of the default Derby, update the hapi.properties file to have the following: To configure the starter app to use PostgreSQL, instead of the default H2, update the application.yaml file to have the following:
- datasource.driver=org.postgresql.Driver ```yaml
- datasource.url=jdbc:postgresql://localhost:5432/hapi_dstu3 spring:
- hibernate.dialect=org.hibernate.dialect.PostgreSQL95Dialect datasource:
- datasource.username=admin url: 'jdbc:postgresql://localhost:5432/hapi_dstu3'
- datasource.password=admin username: admin
password: admin
driverClassName: org.postgresql.Driver
```
Because the integration tests within the project rely on the default Derby database configuration, it is important to either explicity skip the integration tests during the build process, i.e., `mvn install -DskipTests`, or delete the tests altogether. Failure to skip or delete the tests once you've configured PostgreSQL for the datasource.driver, datasource.url, and hibernate.dialect as outlined above will result in build errors and compilation failure. Because the integration tests within the project rely on the default H2 database configuration, it is important to either explicity skip the integration tests during the build process, i.e., `mvn install -DskipTests`, or delete the tests altogether. Failure to skip or delete the tests once you've configured PostgreSQL for the datasource.driver, datasource.url, and hibernate.dialect as outlined above will result in build errors and compilation failure.
It is important to use PostgreSQL95Dialect when using PostgreSQL version 10+.
## Overriding application properties
You can override the properties that are loaded into the compiled web app (.war file) making a copy of the hapi.properties file on the file system, making changes to it, and then setting the JAVA_OPTS environment variable on the tomcat server to tell hapi-jpaserver-starter where the overriding properties file is. For example:
`-Dhapi.properties=/some/custom/directory/hapi.properties`
Note: This property name and the path is case-sensitive. "-DHAPI.PROPERTIES=XXX" will not work.
## Customizing The Web Testpage UI ## Customizing The Web Testpage UI
@@ -127,7 +187,7 @@ The UI is customized using [Thymeleaf](https://www.thymeleaf.org/) template file
Several template files that can be customized are found in the following directory: [https://github.com/hapifhir/hapi-fhir-jpaserver-starter/tree/master/src/main/webapp/WEB-INF/templates](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/tree/master/src/main/webapp/WEB-INF/templates) Several template files that can be customized are found in the following directory: [https://github.com/hapifhir/hapi-fhir-jpaserver-starter/tree/master/src/main/webapp/WEB-INF/templates](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/tree/master/src/main/webapp/WEB-INF/templates)
## Deploying to a Container ## Deploying to an Application Server
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.
@@ -147,6 +207,8 @@ Again, browse to the following link to use the server (note that the port 8080 m
[http://localhost:8080/hapi-fhir-jpaserver/](http://localhost:8080/hapi-fhir-jpaserver/) [http://localhost:8080/hapi-fhir-jpaserver/](http://localhost:8080/hapi-fhir-jpaserver/)
If you would like it to be hosted at the root, eg. http://localhost:8080/ - then rename the WAR file to ```ROOT.war```.
## Deploy with docker compose ## Deploy with docker compose
Docker compose is a simple option to build and deploy container. To deploy with docker compose, you should build the project Docker compose is a simple option to build and deploy container. To deploy with docker compose, you should build the project
@@ -156,14 +218,20 @@ reached at http://localhost:8080/hapi-fhir-jpaserver/.
In order to use another port, change the `ports` parameter In order to use another port, change the `ports` parameter
inside `docker-compose.yml` to `8888:8080`, where 8888 is a port of your choice. inside `docker-compose.yml` to `8888:8080`, where 8888 is a port of your choice.
The docker compose set also includes my MySQL database, if you choose to use MySQL instead of derby, change the following The docker compose set also includes my MySQL database, if you choose to use MySQL instead of H2, change the following
properties in hapi.properties: properties in application.yaml:
- datasource.driver=com.mysql.jdbc.Driver ```yaml
- datasource.url=jdbc:mysql://hapi-fhir-mysql:3306/hapi spring:
- hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect datasource:
- datasource.username=admin url: 'jdbc:mysql://hapi-fhir-mysql:3306/hapi'
- datasource.password=admin username: admin
password: admin
driverClassName: com.mysql.jdbc.Driver
```
## Running hapi-fhir-jpaserver direclty from IntelliJ as Spring Boot
Make sure you run with the maven profile called ```boot``` and NOT also ```jetty```. Then you are ready to press debug the project directly without any extra Application Servers.
## Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ ## Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ
@@ -201,21 +269,21 @@ It is important to use MySQL5Dialect when using MySQL version 5+.
## Enabling Subscriptions ## Enabling Subscriptions
The server may be configured with subscription support by enabling properties in the [hapi.properties](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/hapi.properties) file: The server may be configured with subscription support by enabling properties in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file:
- `subscription.resthook.enabled` - Enables REST Hook subscriptions, where the server will make an outgoing connection to a remote REST server - `hapi.fhir.subscription.resthook.enabled` - Enables REST Hook subscriptions, where the server will make an outgoing connection to a remote REST server
- `subscription.email.enabled` - Enables email subscriptions. Note that you must also provide the connection details for a usable SMTP server. - `hapi.fhir.subscription.email.*` - Enables email subscriptions. Note that you must also provide the connection details for a usable SMTP server.
- `subscription.websocket.enabled` - Enables websocket subscriptions. With this enabled, your server will accept incoming websocket connections on the following URL (this example uses the default context path and port, you may need to tweak depending on your deployment environment): [ws://localhost:8080/hapi-fhir-jpaserver/websocket](ws://localhost:8080/hapi-fhir-jpaserver/websocket) - `hapi.fhir.subscription.websocket.enabled` - Enables websocket subscriptions. With this enabled, your server will accept incoming websocket connections on the following URL (this example uses the default context path and port, you may need to tweak depending on your deployment environment): [ws://localhost:8080/hapi-fhir-jpaserver/websocket](ws://localhost:8080/hapi-fhir-jpaserver/websocket)
## Enabling EMPI ## Enabling EMPI
Set `empi.enabled=true` in the [hapi.properties](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/hapi.properties) file to enable EMPI on this server. The EMPI matching rules are configured in [empi-rules.json](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/empi-rules.json). The rules in this example file should be replaced with actual matching rules appropriate to your data. Note that EMPI relies on subscriptions, so for EMPI to work, subscriptions must be enabled. Set `hapi.fhir.empi_enabled=true` in the [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml) file to enable EMPI on this server. The EMPI matching rules are configured in [empi-rules.json](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/empi-rules.json). The rules in this example file should be replaced with actual matching rules appropriate to your data. Note that EMPI relies on subscriptions, so for EMPI to work, subscriptions must be enabled.
## Using Elasticsearch ## Using Elasticsearch
By default, the server will use embedded lucene indexes for terminology and fulltext indexing purposes. You can switch this to using lucene by editing the properties in [hapi.properties](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/hapi.properties) By default, the server will use embedded lucene indexes for terminology and fulltext indexing purposes. You can switch this to using lucene by editing the properties in [application.yaml](https://github.com/hapifhir/hapi-fhir-jpaserver-starter/blob/master/src/main/resources/application.yaml)
For example: For example:

132
pom.xml
View File

@@ -3,6 +3,8 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<!-- one-liner to take you to the cloud with settings form the application.yaml file: -->
<!-- 'mvn clean package com.google.cloud.tools:jib-maven-plugin:dockerBuild -Dimage=distroless-hapi && docker run -p 8080:8080 -e spring.batch.job.enabled=false distroless-hapi' -->
<!-- <!--
Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management. Note: HAPI projects use the "hapi-fhir" POM as their base to provide easy management.
You do not need to use this in your own projects, so the "parent" tag and it's You do not need to use this in your own projects, so the "parent" tag and it's
@@ -17,8 +19,13 @@
<artifactId>hapi-fhir-jpaserver-starter</artifactId> <artifactId>hapi-fhir-jpaserver-starter</artifactId>
<properties>
<java.version>8</java.version>
<spring_boot_version>2.3.4.RELEASE</spring_boot_version>
</properties>
<prerequisites> <prerequisites>
<maven>3.5.0</maven> <maven>3.6.3</maven>
</prerequisites> </prerequisites>
<packaging>war</packaging> <packaging>war</packaging>
@@ -69,12 +76,6 @@
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<!--
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
</dependency>
-->
<!-- This dependency includes the core HAPI-FHIR classes --> <!-- This dependency includes the core HAPI-FHIR classes -->
<dependency> <dependency>
@@ -118,7 +119,7 @@
<artifactId>hapi-fhir-testpage-overlay</artifactId> <artifactId>hapi-fhir-testpage-overlay</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
<classifier>classes</classifier> <classifier>classes</classifier>
<scope>provided</scope>
</dependency> </dependency>
<!-- HAPI-FHIR uses Logback for logging support. The logback library is included automatically by Maven as a part of the hapi-fhir-base dependency, but you also need to include a logging library. Logback <!-- HAPI-FHIR uses Logback for logging support. The logback library is included automatically by Maven as a part of the hapi-fhir-base dependency, but you also need to include a logging library. Logback
@@ -240,18 +241,7 @@
<artifactId>jetty-webapp</artifactId> <artifactId>jetty-webapp</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!--
<dependency>
<groupId>com.helger</groupId>
<artifactId>ph-schematron</artifactId>
<exclusions>
<exclusion>
<artifactId>Saxon-HE</artifactId>
<groupId>net.sf.saxon</groupId>
</exclusion>
</exclusions>
</dependency>
-->
<!-- <!--
For some reason JavaDoc crashed during site generation unless we have this dependency For some reason JavaDoc crashed during site generation unless we have this dependency
@@ -276,15 +266,58 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>${spring_boot_version}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring_boot_version}</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<!-- Tells Maven to name the generated WAR file as hapi-fhir-jpaserver.war --> <!-- Tells Maven to name the generated WAR file as ROOT.war -->
<finalName>hapi-fhir-jpaserver</finalName> <finalName>ROOT</finalName>
<plugins> <plugins>
<!-- The following is not required for the application to build, but allows you to test it by issuing "mvn jetty:run" from the command line. --> <plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
<configuration>
<mainClass>ca.uhn.fhir.jpa.starter.Application</mainClass>
</configuration>
</execution>
</executions>
</plugin>
<!-- The following is not required for the application to build, but allows you to test it by issuing "mvn package jetty:run -Dspring.batch.job.enabled=false" from the command line. -->
<!--
<plugin> <plugin>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId> <artifactId>jetty-maven-plugin</artifactId>
@@ -296,14 +329,15 @@
</webApp> </webApp>
</configuration> </configuration>
</plugin> </plugin>
-->
<!-- Tell Maven which Java source version you want to use --> <!-- Tell Maven which Java source version you want to use -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration> <configuration>
<source>1.8</source> <release>8</release>
<target>1.8</target>
</configuration> </configuration>
</plugin> </plugin>
@@ -323,7 +357,7 @@
<artifactId>hapi-fhir-testpage-overlay</artifactId> <artifactId>hapi-fhir-testpage-overlay</artifactId>
</overlay> </overlay>
</overlays> </overlays>
<webXml>src/main/webapp/WEB-INF/web.xml</webXml> <failOnMissingWebXml>false</failOnMissingWebXml>
</configuration> </configuration>
</plugin> </plugin>
@@ -438,9 +472,51 @@
</ignoredResourcePatterns> </ignoredResourcePatterns>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<profiles>
<!-- Package the war for your preference. Use the boot profile if you prefer a single jar/war
that can be started with and embedded application server. Default is jetty as it is assumed
that the main users of this project already have an app server.
Different profiles are needed as packing it for spring boot, makes the resulting war undeployable
due to a class shading issue between tomcat and jetty.
(the error is 'java.util.ServiceConfigurationError: org.apache.juli.logging.Log: org.eclipse.jetty.apache.jsp.JuliLog not a subtype')
-->
<!-- example of how to start the server using spring boot-->
<!-- mvn clean package spring-boot:repackage -Pboot && java -jar target/hapi-fhir-jpaserver.war -->
<!-- Use the boot profile for development and debugging options when using your IDE -->
<profile>
<id>boot</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring_boot_version}</version>
</dependency>
</dependencies>
</profile>
<!-- examples of how to start the server using the default profile-->
<!-- mvn clean package jetty:run -Dspring.batch.job.enabled=false -->
<!-- java -jar -Dspring.batch.job.enabled=false jetty-runner.jar target/hapi-fhir-jpaserver.war -->
<profile>
<id>jetty</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring_boot_version}</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</profile>
</profiles>
</project> </project>

View File

@@ -1,13 +1,20 @@
package ca.uhn.fhir.jpa.empi; package ca.uhn.fhir.jpa.empi;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.empi.api.IEmpiSettings; import ca.uhn.fhir.empi.api.IEmpiSettings;
import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator; import ca.uhn.fhir.empi.rules.config.EmpiRuleValidator;
import ca.uhn.fhir.empi.rules.config.EmpiSettings; import ca.uhn.fhir.empi.rules.config.EmpiSettings;
import ca.uhn.fhir.jpa.starter.HapiProperties; import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.rest.server.util.ISearchParamRetriever;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
@@ -18,14 +25,21 @@ import java.io.IOException;
* in 5.1.0 picks this up even if EMPI is disabled currently. * in 5.1.0 picks this up even if EMPI is disabled currently.
*/ */
@Configuration @Configuration
@Conditional(EmpiConfigCondition.class)
@Import({EmpiConsumerConfig.class, EmpiSubmitterConfig.class})
public class EmpiConfig { public class EmpiConfig {
@Bean @Bean
IEmpiSettings empiSettings(EmpiRuleValidator theEmpiRuleValidator) throws IOException { EmpiRuleValidator empiRuleValidator(FhirContext theFhirContext, ISearchParamRetriever theSearchParamRetriever) {
return new EmpiRuleValidator(theFhirContext, theSearchParamRetriever);
}
@Bean
IEmpiSettings empiSettings(@Autowired EmpiRuleValidator theEmpiRuleValidator, AppProperties appProperties) throws IOException {
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("empi-rules.json"); Resource resource = resourceLoader.getResource("empi-rules.json");
String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8); String json = IOUtils.toString(resource.getInputStream(), Charsets.UTF_8);
return new EmpiSettings(theEmpiRuleValidator).setEnabled(HapiProperties.getEmpiEnabled()).setScriptText(json); return new EmpiSettings(theEmpiRuleValidator).setEnabled(appProperties.getEmpi_enabled()).setScriptText(json);
} }
} }

View File

@@ -0,0 +1,13 @@
package ca.uhn.fhir.jpa.empi;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class EmpiConfigCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {
String property = conditionContext.getEnvironment().getProperty("hapi.fhir.empi_enabled");
return Boolean.parseBoolean(property);
}
}

View File

@@ -0,0 +1,669 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.api.config.DaoConfig.ClientIdStrategyEnum;
import ca.uhn.fhir.rest.api.EncodingEnum;
import java.util.ArrayList;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.hl7.fhir.r4.model.Bundle;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@ConfigurationProperties(prefix = "hapi.fhir")
@Configuration
@EnableConfigurationProperties
public class AppProperties {
private Boolean empi_enabled = false;
private Boolean allow_cascading_deletes = false;
private Boolean allow_contains_searches = true;
private Boolean allow_external_references = false;
private Boolean allow_multiple_delete = false;
private Boolean allow_override_default_search_params = true;
private Boolean allow_placeholder_references = true;
private Boolean auto_create_placeholder_reference_targets = true;
private Boolean enable_index_missing_fields = false;
private Boolean enforce_referential_integrity_on_delete = true;
private Boolean enforce_referential_integrity_on_write = true;
private Boolean etag_support_enabled = true;
private Boolean expunge_enabled = true;
private Boolean fhirpath_interceptor_enabled = false;
private Boolean filter_search_enabled = true;
private Boolean graphql_enabled = false;
private Boolean binary_storage_enabled = false;
private Boolean bulk_export_enabled = false;
private Boolean default_pretty_print = true;
private Integer default_page_size = 20;
private Integer max_binary_size = null;
private Integer max_page_size = Integer.MAX_VALUE;
private Integer defer_indexing_for_codesystems_of_size = 100;
private Long retain_cached_searches_mins = 60L;
private Long reuse_cached_search_results_millis = 60000L;
private String server_address = null;
private EncodingEnum default_encoding = EncodingEnum.JSON;
private FhirVersionEnum fhir_version = FhirVersionEnum.R4;
private ClientIdStrategyEnum client_id_strategy = ClientIdStrategyEnum.ALPHANUMERIC;
private List<String> supported_resource_types = new ArrayList<>();
private List<Bundle.BundleType> allowed_bundle_types = null;
private Validation validation = new Validation();
private List<Tester> tester = ImmutableList.of(new Tester());
private Logger logger = new Logger();
private Subscription subscription = new Subscription();
private Cors cors = null;
private Partitioning partitioning = null;
private List<ImplementationGuide> implementationGuides = null;
public Integer getDefer_indexing_for_codesystems_of_size() {
return defer_indexing_for_codesystems_of_size;
}
public void setDefer_indexing_for_codesystems_of_size(Integer defer_indexing_for_codesystems_of_size) {
this.defer_indexing_for_codesystems_of_size = defer_indexing_for_codesystems_of_size;
}
public List<ImplementationGuide> getImplementationGuides() {
return implementationGuides;
}
public void setImplementationGuides(List<ImplementationGuide> implementationGuides) {
this.implementationGuides = implementationGuides;
}
public Partitioning getPartitioning() {
return partitioning;
}
public void setPartitioning(Partitioning partitioning) {
this.partitioning = partitioning;
}
public Boolean getEmpi_enabled() {
return empi_enabled;
}
public void setEmpi_enabled(Boolean empi_enabled) {
this.empi_enabled = empi_enabled;
}
public Cors getCors() {
return cors;
}
public void setCors(Cors cors) {
this.cors = cors;
}
public List<Bundle.BundleType> getAllowed_bundle_types() {
return allowed_bundle_types;
}
public void setAllowed_bundle_types(List<Bundle.BundleType> allowed_bundle_types) {
this.allowed_bundle_types = allowed_bundle_types;
}
public String getServer_address() {
return server_address;
}
public void setServer_address(String server_address) {
this.server_address = server_address;
}
public Subscription getSubscription() {
return subscription;
}
public Boolean getDefault_pretty_print() {
return default_pretty_print;
}
public void setDefault_pretty_print(Boolean default_pretty_print) {
this.default_pretty_print = default_pretty_print;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
public Validation getValidation() {
return validation;
}
public void setValidation(Validation validation) {
this.validation = validation;
}
public List<String> getSupported_resource_types() {
return supported_resource_types;
}
public void setSupported_resource_types(List<String> supported_resource_types) {
this.supported_resource_types = supported_resource_types;
}
public Logger getLogger() {
return logger;
}
public void setLogger(Logger logger) {
this.logger = logger;
}
public ClientIdStrategyEnum getClient_id_strategy() {
return client_id_strategy;
}
public void setClient_id_strategy(
ClientIdStrategyEnum client_id_strategy) {
this.client_id_strategy = client_id_strategy;
}
public Boolean getAllow_cascading_deletes() {
return allow_cascading_deletes;
}
public void setAllow_cascading_deletes(Boolean allow_cascading_deletes) {
this.allow_cascading_deletes = allow_cascading_deletes;
}
public Boolean getAllow_contains_searches() {
return allow_contains_searches;
}
public void setAllow_contains_searches(Boolean allow_contains_searches) {
this.allow_contains_searches = allow_contains_searches;
}
public Boolean getAllow_external_references() {
return allow_external_references;
}
public void setAllow_external_references(Boolean allow_external_references) {
this.allow_external_references = allow_external_references;
}
public Boolean getAllow_multiple_delete() {
return allow_multiple_delete;
}
public void setAllow_multiple_delete(Boolean allow_multiple_delete) {
this.allow_multiple_delete = allow_multiple_delete;
}
public Boolean getAllow_override_default_search_params() {
return allow_override_default_search_params;
}
public void setAllow_override_default_search_params(
Boolean allow_override_default_search_params) {
this.allow_override_default_search_params = allow_override_default_search_params;
}
public Boolean getAllow_placeholder_references() {
return allow_placeholder_references;
}
public void setAllow_placeholder_references(Boolean allow_placeholder_references) {
this.allow_placeholder_references = allow_placeholder_references;
}
public Boolean getAuto_create_placeholder_reference_targets() {
return auto_create_placeholder_reference_targets;
}
public void setAuto_create_placeholder_reference_targets(
Boolean auto_create_placeholder_reference_targets) {
this.auto_create_placeholder_reference_targets = auto_create_placeholder_reference_targets;
}
public Integer getDefault_page_size() {
return default_page_size;
}
public void setDefault_page_size(Integer default_page_size) {
this.default_page_size = default_page_size;
}
public Boolean getEnable_index_missing_fields() {
return enable_index_missing_fields;
}
public void setEnable_index_missing_fields(Boolean enable_index_missing_fields) {
this.enable_index_missing_fields = enable_index_missing_fields;
}
public Boolean getEnforce_referential_integrity_on_delete() {
return enforce_referential_integrity_on_delete;
}
public void setEnforce_referential_integrity_on_delete(
Boolean enforce_referential_integrity_on_delete) {
this.enforce_referential_integrity_on_delete = enforce_referential_integrity_on_delete;
}
public Boolean getEnforce_referential_integrity_on_write() {
return enforce_referential_integrity_on_write;
}
public void setEnforce_referential_integrity_on_write(
Boolean enforce_referential_integrity_on_write) {
this.enforce_referential_integrity_on_write = enforce_referential_integrity_on_write;
}
public Boolean getEtag_support_enabled() {
return etag_support_enabled;
}
public void setEtag_support_enabled(Boolean etag_support_enabled) {
this.etag_support_enabled = etag_support_enabled;
}
public Boolean getExpunge_enabled() {
return expunge_enabled;
}
public void setExpunge_enabled(Boolean expunge_enabled) {
this.expunge_enabled = expunge_enabled;
}
public Boolean getFhirpath_interceptor_enabled() {
return fhirpath_interceptor_enabled;
}
public void setFhirpath_interceptor_enabled(Boolean fhirpath_interceptor_enabled) {
this.fhirpath_interceptor_enabled = fhirpath_interceptor_enabled;
}
public Boolean getFilter_search_enabled() {
return filter_search_enabled;
}
public void setFilter_search_enabled(Boolean filter_search_enabled) {
this.filter_search_enabled = filter_search_enabled;
}
public Boolean getGraphql_enabled() {
return graphql_enabled;
}
public void setGraphql_enabled(Boolean graphql_enabled) {
this.graphql_enabled = graphql_enabled;
}
public Boolean getBinary_storage_enabled() {
return binary_storage_enabled;
}
public void setBinary_storage_enabled(Boolean binary_storage_enabled) {
this.binary_storage_enabled = binary_storage_enabled;
}
public Boolean getBulk_export_enabled() {
return bulk_export_enabled;
}
public void setBulk_export_enabled(Boolean bulk_export_enabled) {
this.bulk_export_enabled = bulk_export_enabled;
}
public EncodingEnum getDefault_encoding() {
return default_encoding;
}
public void setDefault_encoding(EncodingEnum default_encoding) {
this.default_encoding = default_encoding;
}
public FhirVersionEnum getFhir_version() {
return fhir_version;
}
public void setFhir_version(FhirVersionEnum fhir_version) {
this.fhir_version = fhir_version;
}
public Integer getMax_binary_size() {
return max_binary_size;
}
public void setMax_binary_size(Integer max_binary_size) {
this.max_binary_size = max_binary_size;
}
public Integer getMax_page_size() {
return max_page_size;
}
public void setMax_page_size(Integer max_page_size) {
this.max_page_size = max_page_size;
}
public Long getRetain_cached_searches_mins() {
return retain_cached_searches_mins;
}
public void setRetain_cached_searches_mins(Long retain_cached_searches_mins) {
this.retain_cached_searches_mins = retain_cached_searches_mins;
}
public Long getReuse_cached_search_results_millis() {
return reuse_cached_search_results_millis;
}
public void setReuse_cached_search_results_millis(Long reuse_cached_search_results_millis) {
this.reuse_cached_search_results_millis = reuse_cached_search_results_millis;
}
public List<Tester> getTester() {
return tester;
}
public void setTester(List<Tester> tester) {
this.tester = tester;
}
public static class Cors {
private Boolean allow_Credentials = true;
private List<String> allowed_origin = ImmutableList.of("*");
public List<String> getAllowed_origin() {
return allowed_origin;
}
public void setAllowed_origin(List<String> allowed_origin) {
this.allowed_origin = allowed_origin;
}
public Boolean getAllow_Credentials() {
return allow_Credentials;
}
public void setAllow_Credentials(Boolean allow_Credentials) {
this.allow_Credentials = allow_Credentials;
}
}
public static class Logger {
private String name = "fhirtest.access";
private String error_format = "ERROR - ${requestVerb} ${requestUrl}";
private String format = "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]";
private Boolean log_exceptions = true;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getError_format() {
return error_format;
}
public void setError_format(String error_format) {
this.error_format = error_format;
}
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public Boolean getLog_exceptions() {
return log_exceptions;
}
public void setLog_exceptions(Boolean log_exceptions) {
this.log_exceptions = log_exceptions;
}
}
public static class Tester {
private String id = "home";
private String name = "Local Tester";
private String server_address = "http://localhost:8080/fhir";
private Boolean refuse_to_fetch_third_party_urls = true;
private FhirVersionEnum fhir_version = FhirVersionEnum.R4;
public FhirVersionEnum getFhir_version() {
return fhir_version;
}
public void setFhir_version(FhirVersionEnum fhir_version) {
this.fhir_version = fhir_version;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getServer_address() {
return server_address;
}
public void setServer_address(String server_address) {
this.server_address = server_address;
}
public Boolean getRefuse_to_fetch_third_party_urls() {
return refuse_to_fetch_third_party_urls;
}
public void setRefuse_to_fetch_third_party_urls(Boolean refuse_to_fetch_third_party_urls) {
this.refuse_to_fetch_third_party_urls = refuse_to_fetch_third_party_urls;
}
}
public static class ImplementationGuide
{
private String url;
private String name;
private String version;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
public static class Validation {
private Boolean requests_enabled = false;
private Boolean responses_enabled = false;
public Boolean getRequests_enabled() {
return requests_enabled;
}
public void setRequests_enabled(Boolean requests_enabled) {
this.requests_enabled = requests_enabled;
}
public Boolean getResponses_enabled() {
return responses_enabled;
}
public void setResponses_enabled(Boolean responses_enabled) {
this.responses_enabled = responses_enabled;
}
}
public static class Partitioning {
private Boolean partitioning_include_in_search_hashes = false;
public Boolean getPartitioning_include_in_search_hashes() {
return partitioning_include_in_search_hashes;
}
public void setPartitioning_include_in_search_hashes(Boolean partitioning_include_in_search_hashes) {
this.partitioning_include_in_search_hashes = partitioning_include_in_search_hashes;
}
}
public static class Subscription {
public Boolean getResthook_enabled() {
return resthook_enabled;
}
public void setResthook_enabled(Boolean resthook_enabled) {
this.resthook_enabled = resthook_enabled;
}
public Boolean getWebsocket_enabled() {
return websocket_enabled;
}
public void setWebsocket_enabled(Boolean websocket_enabled) {
this.websocket_enabled = websocket_enabled;
}
private Boolean resthook_enabled = false;
private Boolean websocket_enabled = false;
private Email email = null;
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
public static class Email {
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Boolean getAuth() {
return auth;
}
public void setAuth(Boolean auth) {
this.auth = auth;
}
public Boolean getStartTlsEnable() {
return startTlsEnable;
}
public void setStartTlsEnable(Boolean startTlsEnable) {
this.startTlsEnable = startTlsEnable;
}
public Boolean getStartTlsRequired() {
return startTlsRequired;
}
public void setStartTlsRequired(Boolean startTlsRequired) {
this.startTlsRequired = startTlsRequired;
}
public Boolean getQuitWait() {
return quitWait;
}
public void setQuitWait(Boolean quitWait) {
this.quitWait = quitWait;
}
private String from;
private String host;
private Integer port = 25;
private String username;
private String password;
private Boolean auth = false;
private Boolean startTlsEnable = false;
private Boolean startTlsRequired = false;
private Boolean quitWait = false;
}
}
}

View File

@@ -0,0 +1,76 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.jpa.empi.EmpiConfig;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
@ServletComponentScan(basePackageClasses = {
JpaRestfulServer.class})
@SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class})
@Import({SubscriptionSubmitterConfig.class, SubscriptionProcessorConfig.class, SubscriptionChannelConfig.class, WebsocketDispatcherConfig.class, EmpiConfig.class})
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
System.setProperty("spring.batch.job.enabled", "false");
SpringApplication.run(Application.class, args);
//Server is now accessible at eg. http://localhost:8080/hapi-fhir-jpaserver/fhir/metadata
//UI is now accessible at http://localhost:8080/hapi-fhir-jpaserver/
}
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
@Autowired
AutowireCapableBeanFactory beanFactory;
@Bean
public ServletRegistrationBean hapiServletRegistration() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
JpaRestfulServer jpaRestfulServer = new JpaRestfulServer();
beanFactory.autowireBean(jpaRestfulServer);
servletRegistrationBean.setServlet(jpaRestfulServer);
servletRegistrationBean.addUrlMappings("/fhir/*");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
@Bean
public ServletRegistrationBean overlayRegistrationBean() {
AnnotationConfigWebApplicationContext annotationConfigWebApplicationContext = new AnnotationConfigWebApplicationContext();
annotationConfigWebApplicationContext.register(FhirTesterConfig.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(
annotationConfigWebApplicationContext);
dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class);
dispatcherServlet.setContextConfigLocation(FhirTesterConfig.class.getName());
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setServlet(dispatcherServlet);
registrationBean.addUrlMappings("/*");
registrationBean.setLoadOnStartup(1);
return registrationBean;
}
}

View File

@@ -1,49 +0,0 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.empi.EmpiConfig;
import ca.uhn.fhir.jpa.empi.config.EmpiConsumerConfig;
import ca.uhn.fhir.jpa.empi.config.EmpiSubmitterConfig;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
public class ApplicationContext extends AnnotationConfigWebApplicationContext {
public ApplicationContext() {
FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion();
if (fhirVersion == FhirVersionEnum.DSTU2) {
register(FhirServerConfigDstu2.class, FhirServerConfigCommon.class);
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
register(FhirServerConfigDstu3.class, FhirServerConfigCommon.class);
} else if (fhirVersion == FhirVersionEnum.R4) {
register(FhirServerConfigR4.class, FhirServerConfigCommon.class);
} else if (fhirVersion == FhirVersionEnum.R5) {
register(FhirServerConfigR5.class, FhirServerConfigCommon.class);
} else {
throw new IllegalStateException();
}
if (HapiProperties.getSubscriptionWebsocketEnabled()) {
register(WebsocketDispatcherConfig.class);
}
if (HapiProperties.getSubscriptionEmailEnabled()
|| HapiProperties.getSubscriptionRestHookEnabled()
|| HapiProperties.getSubscriptionWebsocketEnabled()) {
register(SubscriptionSubmitterConfig.class);
register(SubscriptionProcessorConfig.class);
register(SubscriptionChannelConfig.class);
}
if (HapiProperties.getEmpiEnabled()) {
register(EmpiSubmitterConfig.class);
register(EmpiConsumerConfig.class);
register(EmpiConfig.class);
}
}
}

View File

@@ -10,39 +10,33 @@ import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor; import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.bulk.provider.BulkDataExportProvider; import ca.uhn.fhir.jpa.bulk.provider.BulkDataExportProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
import ca.uhn.fhir.jpa.packages.PackageInstallationSpec;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider; import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.IJpaSystemProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2; import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider; import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider; import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3; import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4; import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5; import ca.uhn.fhir.jpa.provider.r5.JpaConformanceProviderR5;
import ca.uhn.fhir.jpa.provider.r5.JpaSystemProviderR5;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor; import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor; import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import ca.uhn.fhir.validation.IValidatorModule; import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ResultSeverityEnum;
import org.hl7.fhir.dstu3.model.Bundle; import com.google.common.base.Strings;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.Bundle.BundleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfiguration;
@@ -50,12 +44,67 @@ import org.springframework.web.cors.CorsConfiguration;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.List;
import java.util.Set; import java.util.Optional;
import java.util.TreeSet; import java.util.stream.Collectors;
public class BaseJpaRestfulServer extends RestfulServer { public class BaseJpaRestfulServer extends RestfulServer {
@Autowired
DaoRegistry daoRegistry;
@Autowired
DaoConfig daoConfig;
@Autowired
ISearchParamRegistry searchParamRegistry;
@Autowired
IFhirSystemDao fhirSystemDao;
@Autowired
ResourceProviderFactory resourceProviders;
@Autowired
IJpaSystemProvider jpaSystemProvider;
@Autowired
IInterceptorBroadcaster interceptorBroadcaster;
@Autowired
DatabaseBackedPagingProvider databaseBackedPagingProvider;
@Autowired
IInterceptorService interceptorService;
@Autowired
IValidatorModule validatorModule;
@Autowired
Optional<GraphQLProvider> graphQLProvider;
@Autowired
BulkDataExportProvider bulkDataExportProvider;
@Autowired
PartitionManagementProvider partitionManagementProvider;
@Autowired
BinaryStorageInterceptor binaryStorageInterceptor;
@Autowired
IPackageInstallerSvc packageInstallerSvc;
@Autowired
AppProperties appProperties;
@Autowired
ApplicationContext myApplicationContext;
public BaseJpaRestfulServer() {
}
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -67,47 +116,19 @@ public class BaseJpaRestfulServer extends RestfulServer {
* Create a FhirContext object that uses the version of FHIR * Create a FhirContext object that uses the version of FHIR
* specified in the properties file. * specified in the properties file.
*/ */
ApplicationContext appCtx = (ApplicationContext) getServletContext()
.getAttribute("org.springframework.web.context.WebApplicationContext.ROOT");
// Customize supported resource types // Customize supported resource types
Set<String> supportedResourceTypes = HapiProperties.getSupportedResourceTypes(); List<String> supportedResourceTypes = appProperties.getSupported_resource_types();
if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) { if (!supportedResourceTypes.isEmpty() && !supportedResourceTypes.contains("SearchParameter")) {
supportedResourceTypes.add("SearchParameter"); supportedResourceTypes.add("SearchParameter");
}
if (!supportedResourceTypes.isEmpty()) {
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
daoRegistry.setSupportedResourceTypes(supportedResourceTypes); daoRegistry.setSupportedResourceTypes(supportedResourceTypes);
} }
/* setFhirContext(fhirSystemDao.getContext());
* ResourceProviders are fetched from the Spring context
*/
FhirVersionEnum fhirVersion = HapiProperties.getFhirVersion();
ResourceProviderFactory resourceProviders;
Object systemProvider;
if (fhirVersion == FhirVersionEnum.DSTU2) {
resourceProviders = appCtx.getBean("myResourceProvidersDstu2", ResourceProviderFactory.class);
systemProvider = appCtx.getBean("mySystemProviderDstu2", JpaSystemProviderDstu2.class);
} else if (fhirVersion == FhirVersionEnum.DSTU3) {
resourceProviders = appCtx.getBean("myResourceProvidersDstu3", ResourceProviderFactory.class);
systemProvider = appCtx.getBean("mySystemProviderDstu3", JpaSystemProviderDstu3.class);
} else if (fhirVersion == FhirVersionEnum.R4) {
resourceProviders = appCtx.getBean("myResourceProvidersR4", ResourceProviderFactory.class);
systemProvider = appCtx.getBean("mySystemProviderR4", JpaSystemProviderR4.class);
} else if (fhirVersion == FhirVersionEnum.R5) {
resourceProviders = appCtx.getBean("myResourceProvidersR5", ResourceProviderFactory.class);
systemProvider = appCtx.getBean("mySystemProviderR5", JpaSystemProviderR5.class);
} else {
throw new IllegalStateException();
}
setFhirContext(appCtx.getBean(FhirContext.class));
registerProviders(resourceProviders.createProviders()); registerProviders(resourceProviders.createProviders());
registerProvider(systemProvider); registerProvider(jpaSystemProvider);
FhirVersionEnum fhirVersion = fhirSystemDao.getContext().getVersion().getVersion();
/* /*
* The conformance provider exports the supported resources, search parameters, etc for * The conformance provider exports the supported resources, search parameters, etc for
* this server. The JPA version adds resourceProviders counts to the exported statement, so it * this server. The JPA version adds resourceProviders counts to the exported statement, so it
@@ -116,27 +137,31 @@ public class BaseJpaRestfulServer extends RestfulServer {
* You can also create your own subclass of the conformance provider if you need to * You can also create your own subclass of the conformance provider if you need to
* provide further customization of your server's CapabilityStatement * provide further customization of your server's CapabilityStatement
*/ */
DaoConfig daoConfig = appCtx.getBean(DaoConfig.class);
ISearchParamRegistry searchParamRegistry = appCtx.getBean(ISearchParamRegistry.class);
if (fhirVersion == FhirVersionEnum.DSTU2) { if (fhirVersion == FhirVersionEnum.DSTU2) {
IFhirSystemDao<ca.uhn.fhir.model.dstu2.resource.Bundle, MetaDt> systemDao = appCtx.getBean("mySystemDaoDstu2", IFhirSystemDao.class);
JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, systemDao, daoConfig); JpaConformanceProviderDstu2 confProvider = new JpaConformanceProviderDstu2(this, fhirSystemDao,
daoConfig);
confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server"); confProvider.setImplementationDescription("HAPI FHIR DSTU2 Server");
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
} else { } else {
if (fhirVersion == FhirVersionEnum.DSTU3) { if (fhirVersion == FhirVersionEnum.DSTU3) {
IFhirSystemDao<Bundle, Meta> systemDao = appCtx.getBean("mySystemDaoDstu3", IFhirSystemDao.class);
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, systemDao, daoConfig, searchParamRegistry); JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(this, fhirSystemDao,
daoConfig, searchParamRegistry);
confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server"); confProvider.setImplementationDescription("HAPI FHIR DSTU3 Server");
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
} else if (fhirVersion == FhirVersionEnum.R4) { } else if (fhirVersion == FhirVersionEnum.R4) {
IFhirSystemDao<org.hl7.fhir.r4.model.Bundle, org.hl7.fhir.r4.model.Meta> systemDao = appCtx.getBean("mySystemDaoR4", IFhirSystemDao.class);
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, systemDao, daoConfig, searchParamRegistry); JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(this, fhirSystemDao,
daoConfig, searchParamRegistry);
confProvider.setImplementationDescription("HAPI FHIR R4 Server"); confProvider.setImplementationDescription("HAPI FHIR R4 Server");
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
} else if (fhirVersion == FhirVersionEnum.R5) { } else if (fhirVersion == FhirVersionEnum.R5) {
IFhirSystemDao<org.hl7.fhir.r5.model.Bundle, org.hl7.fhir.r5.model.Meta> systemDao = appCtx.getBean("mySystemDaoR5", IFhirSystemDao.class);
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, systemDao, daoConfig, searchParamRegistry); JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(this, fhirSystemDao,
daoConfig, searchParamRegistry);
confProvider.setImplementationDescription("HAPI FHIR R5 Server"); confProvider.setImplementationDescription("HAPI FHIR R5 Server");
setServerConformanceProvider(confProvider); setServerConformanceProvider(confProvider);
} else { } else {
@@ -147,7 +172,10 @@ public class BaseJpaRestfulServer extends RestfulServer {
/* /*
* ETag Support * ETag Support
*/ */
setETagSupport(HapiProperties.getEtagSupport());
if (appProperties.getEtag_support_enabled() == false)
setETagSupport(ETagSupportEnum.DISABLED);
/* /*
* This server tries to dynamically generate narratives * This server tries to dynamically generate narratives
@@ -158,12 +186,12 @@ public class BaseJpaRestfulServer extends RestfulServer {
/* /*
* Default to JSON and pretty printing * Default to JSON and pretty printing
*/ */
setDefaultPrettyPrint(HapiProperties.getDefaultPrettyPrint()); setDefaultPrettyPrint(appProperties.getDefault_pretty_print());
/* /*
* Default encoding * Default encoding
*/ */
setDefaultResponseEncoding(HapiProperties.getDefaultEncoding()); setDefaultResponseEncoding(appProperties.getDefault_encoding());
/* /*
* This configures the server to page search results to and from * This configures the server to page search results to and from
@@ -171,7 +199,8 @@ public class BaseJpaRestfulServer extends RestfulServer {
* a performance hit when performing searches that return lots of results, * a performance hit when performing searches that return lots of results,
* but makes the server much more scalable. * but makes the server much more scalable.
*/ */
setPagingProvider(appCtx.getBean(DatabaseBackedPagingProvider.class));
setPagingProvider(databaseBackedPagingProvider);
/* /*
* This interceptor formats the output using nice colourful * This interceptor formats the output using nice colourful
@@ -181,7 +210,7 @@ public class BaseJpaRestfulServer extends RestfulServer {
ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor(); ResponseHighlighterInterceptor responseHighlighterInterceptor = new ResponseHighlighterInterceptor();
this.registerInterceptor(responseHighlighterInterceptor); this.registerInterceptor(responseHighlighterInterceptor);
if (HapiProperties.isFhirPathFilterInterceptorEnabled()) { if (appProperties.getFhirpath_interceptor_enabled()) {
registerInterceptor(new FhirPathFilterInterceptor()); registerInterceptor(new FhirPathFilterInterceptor());
} }
@@ -189,10 +218,10 @@ public class BaseJpaRestfulServer extends RestfulServer {
* Add some logging for each request * Add some logging for each request
*/ */
LoggingInterceptor loggingInterceptor = new LoggingInterceptor(); LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
loggingInterceptor.setLoggerName(HapiProperties.getLoggerName()); loggingInterceptor.setLoggerName(appProperties.getLogger().getName());
loggingInterceptor.setMessageFormat(HapiProperties.getLoggerFormat()); loggingInterceptor.setMessageFormat(appProperties.getLogger().getFormat());
loggingInterceptor.setErrorMessageFormat(HapiProperties.getLoggerErrorFormat()); loggingInterceptor.setErrorMessageFormat(appProperties.getLogger().getError_format());
loggingInterceptor.setLogExceptions(HapiProperties.getLoggerLogExceptions()); loggingInterceptor.setLogExceptions(appProperties.getLogger().getLog_exceptions());
this.registerInterceptor(loggingInterceptor); this.registerInterceptor(loggingInterceptor);
/* /*
@@ -201,8 +230,8 @@ public class BaseJpaRestfulServer extends RestfulServer {
* this doesn't always work. If you are setting links in your search bundles that * this doesn't always work. If you are setting links in your search bundles that
* just refer to "localhost", you might want to use a server address strategy: * just refer to "localhost", you might want to use a server address strategy:
*/ */
String serverAddress = HapiProperties.getServerAddress(); String serverAddress = appProperties.getServer_address();
if (serverAddress != null && serverAddress.length() > 0) { if (!Strings.isNullOrEmpty(serverAddress)) {
setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress)); setServerAddressStrategy(new HardcodedServerAddressStrategy(serverAddress));
} }
@@ -213,22 +242,20 @@ public class BaseJpaRestfulServer extends RestfulServer {
* so it is a potential security vulnerability. Consider using an AuthorizationInterceptor * so it is a potential security vulnerability. Consider using an AuthorizationInterceptor
* with this feature. * with this feature.
*/ */
if (false) { // <-- DISABLED RIGHT NOW if (ctx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { // <-- ENABLED RIGHT NOW
registerProvider(appCtx.getBean(TerminologyUploaderProvider.class)); registerProvider(myApplicationContext.getBean(TerminologyUploaderProvider.class));
} }
// If you want to enable the $trigger-subscription operation to allow // If you want to enable the $trigger-subscription operation to allow
// manual triggering of a subscription delivery, enable this provider // manual triggering of a subscription delivery, enable this provider
if (false) { // <-- DISABLED RIGHT NOW if (true) { // <-- ENABLED RIGHT NOW
SubscriptionTriggeringProvider retriggeringProvider = appCtx registerProvider(myApplicationContext.getBean(SubscriptionTriggeringProvider.class));
.getBean(SubscriptionTriggeringProvider.class);
registerProvider(retriggeringProvider);
} }
// Define your CORS configuration. This is an example // Define your CORS configuration. This is an example
// showing a typical setup. You should customize this // showing a typical setup. You should customize this
// to your specific needs // to your specific needs
if (HapiProperties.getCorsEnabled()) { if (appProperties.getCors() != null) {
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.addAllowedHeader(HttpHeaders.ORIGIN); config.addAllowedHeader(HttpHeaders.ORIGIN);
config.addAllowedHeader(HttpHeaders.ACCEPT); config.addAllowedHeader(HttpHeaders.ACCEPT);
@@ -238,17 +265,15 @@ public class BaseJpaRestfulServer extends RestfulServer {
config.addAllowedHeader("x-fhir-starter"); config.addAllowedHeader("x-fhir-starter");
config.addAllowedHeader("X-Requested-With"); config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Prefer"); config.addAllowedHeader("Prefer");
String allAllowedCORSOrigins = HapiProperties.getCorsAllowedOrigin(); List<String> allAllowedCORSOrigins = appProperties.getCors().getAllowed_origin();
Arrays.stream(allAllowedCORSOrigins.split(",")).forEach(o -> { allAllowedCORSOrigins.forEach(config::addAllowedOrigin);
config.addAllowedOrigin(o);
});
config.addAllowedOrigin(HapiProperties.getCorsAllowedOrigin());
config.addExposedHeader("Location"); config.addExposedHeader("Location");
config.addExposedHeader("Content-Location"); config.addExposedHeader("Content-Location");
config.setAllowedMethods( config.setAllowedMethods(
Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD")); Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "HEAD"));
config.setAllowCredentials(HapiProperties.getCorsAllowedCredentials()); config.setAllowCredentials(appProperties.getCors().getAllow_Credentials());
// Create the interceptor and register it // Create the interceptor and register it
CorsInterceptor interceptor = new CorsInterceptor(config); CorsInterceptor interceptor = new CorsInterceptor(config);
@@ -257,39 +282,35 @@ public class BaseJpaRestfulServer extends RestfulServer {
// If subscriptions are enabled, we want to register the interceptor that // If subscriptions are enabled, we want to register the interceptor that
// will activate them and match results against them // will activate them and match results against them
if (HapiProperties.getSubscriptionWebsocketEnabled() || if (appProperties.getSubscription() != null) {
HapiProperties.getSubscriptionEmailEnabled() ||
HapiProperties.getSubscriptionRestHookEnabled()) {
// Subscription debug logging // Subscription debug logging
IInterceptorService interceptorService = appCtx.getBean(IInterceptorService.class);
interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor()); interceptorService.registerInterceptor(new SubscriptionDebugLogInterceptor());
} }
// Cascading deletes // Cascading deletes
DaoRegistry daoRegistry = appCtx.getBean(DaoRegistry.class);
IInterceptorBroadcaster interceptorBroadcaster = appCtx.getBean(IInterceptorBroadcaster.class);
if (HapiProperties.getAllowCascadingDeletes()) { if (appProperties.getAllow_cascading_deletes()) {
CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx, daoRegistry, interceptorBroadcaster); CascadingDeleteInterceptor cascadingDeleteInterceptor = new CascadingDeleteInterceptor(ctx,
daoRegistry, interceptorBroadcaster);
getInterceptorService().registerInterceptor(cascadingDeleteInterceptor); getInterceptorService().registerInterceptor(cascadingDeleteInterceptor);
} }
// Binary Storage // Binary Storage
if (HapiProperties.isBinaryStorageEnabled()) { if (appProperties.getBinary_storage_enabled()) {
BinaryStorageInterceptor binaryStorageInterceptor = appCtx
.getBean(BinaryStorageInterceptor.class);
getInterceptorService().registerInterceptor(binaryStorageInterceptor); getInterceptorService().registerInterceptor(binaryStorageInterceptor);
} }
// Validation // Validation
IValidatorModule validatorModule = appCtx.getBean(IValidatorModule.class);
if (validatorModule != null) { if (validatorModule != null) {
if (HapiProperties.getValidateRequestsEnabled()) { if (appProperties.getValidation().getRequests_enabled()) {
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor(); RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setValidatorModules(Collections.singletonList(validatorModule)); interceptor.setValidatorModules(Collections.singletonList(validatorModule));
registerInterceptor(interceptor); registerInterceptor(interceptor);
} }
if (HapiProperties.getValidateResponsesEnabled()) { if (appProperties.getValidation().getResponses_enabled()) {
ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor(); ResponseValidatingInterceptor interceptor = new ResponseValidatingInterceptor();
interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR); interceptor.setFailOnSeverity(ResultSeverityEnum.ERROR);
interceptor.setValidatorModules(Collections.singletonList(validatorModule)); interceptor.setValidatorModules(Collections.singletonList(validatorModule));
@@ -298,40 +319,46 @@ public class BaseJpaRestfulServer extends RestfulServer {
} }
// GraphQL // GraphQL
if (HapiProperties.getGraphqlEnabled()) { if (appProperties.getGraphql_enabled()) {
if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { if (fhirVersion.isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
registerProvider(appCtx.getBean(GraphQLProvider.class)); registerProvider(graphQLProvider.get());
} }
} }
if (!HapiProperties.getAllowedBundleTypes().isEmpty()) { if (appProperties.getAllowed_bundle_types() != null) {
String allowedBundleTypesString = HapiProperties.getAllowedBundleTypes(); daoConfig.setBundleTypesAllowedForStorage(appProperties.getAllowed_bundle_types().stream().map(BundleType::toCode).collect(Collectors.toSet()));
Set<String> allowedBundleTypes = new HashSet<>();
Arrays.stream(allowedBundleTypesString.split(",")).forEach(o -> {
BundleType type = BundleType.valueOf(o);
allowedBundleTypes.add(type.toCode());
});
DaoConfig config = daoConfig;
config.setBundleTypesAllowedForStorage(
Collections.unmodifiableSet(new TreeSet<>(allowedBundleTypes)));
} }
daoConfig.setDeferIndexingForCodesystemsOfSize(appProperties.getDefer_indexing_for_codesystems_of_size());
// Bulk Export // Bulk Export
if (HapiProperties.getBulkExportEnabled()) { if (appProperties.getBulk_export_enabled()) {
registerProvider(appCtx.getBean(BulkDataExportProvider.class)); registerProvider(bulkDataExportProvider);
} }
// Partitioning // Partitioning
if (HapiProperties.getPartitioningMultitenancyEnabled()) { if (appProperties.getPartitioning() != null) {
registerInterceptor(new RequestTenantPartitionInterceptor()); registerInterceptor(new RequestTenantPartitionInterceptor());
setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
registerProviders(appCtx.getBean(PartitionManagementProvider.class)); registerProviders(partitionManagementProvider);
} }
if (HapiProperties.getClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY) { if (appProperties.getClient_id_strategy() == DaoConfig.ClientIdStrategyEnum.ANY) {
daoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID); daoConfig.setResourceServerIdStrategy(DaoConfig.IdStrategyEnum.UUID);
daoConfig.setResourceClientIdStrategy(HapiProperties.getClientIdStrategy()); daoConfig.setResourceClientIdStrategy(appProperties.getClient_id_strategy());
}
if (appProperties.getImplementationGuides() != null) {
List<AppProperties.ImplementationGuide> guides = appProperties.getImplementationGuides();
for (AppProperties.ImplementationGuide guide : guides) {
packageInstallerSvc.install(new PackageInstallationSpec()
.setPackageUrl(guide.getUrl())
.setName(guide.getName())
.setVersion(guide.getVersion())
.setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL));
}
} }
} }
} }

View File

@@ -0,0 +1,92 @@
package ca.uhn.fhir.jpa.starter;
import org.springframework.core.env.CompositePropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.PropertySource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class EnvironmentHelper {
public static Properties getHibernateProperties(ConfigurableEnvironment environment) {
Properties properties = new Properties();
if (environment.getProperty("spring.jpa.properties", String.class) == null) {
properties.put("hibernate.search.model_mapping", "ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory");
properties.put("hibernate.format_sql", "false");
properties.put("hibernate.show_sql", "false");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.jdbc.batch_size", "20");
properties.put("hibernate.cache.use_query_cache", "false");
properties.put("hibernate.cache.use_second_level_cache", "false");
properties.put("hibernate.cache.use_structured_entries", "false");
properties.put("hibernate.cache.use_minimal_puts", "false");
properties.put("hibernate.search.default.directory_provider", "filesystem");
properties.put("hibernate.search.default.indexBase", "target/lucenefiles");
properties.put("hibernate.search.lucene_version", "LUCENE_CURRENT");
} else {
Arrays.asList(environment.getProperty("spring.jpa.properties", String.class).split(" ")).stream().forEach(s ->
{
String[] values = s.split("=");
properties.put(values[0], values[1]);
});
}
return properties;
}
public static Map<String, Object> getPropertiesStartingWith(ConfigurableEnvironment aEnv,
String aKeyPrefix) {
Map<String, Object> result = new HashMap<>();
Map<String, Object> map = getAllProperties(aEnv);
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
if (key.startsWith(aKeyPrefix)) {
result.put(key, entry.getValue());
}
}
return result;
}
public static Map<String, Object> getAllProperties(ConfigurableEnvironment aEnv) {
Map<String, Object> result = new HashMap<>();
aEnv.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps)));
return result;
}
public static Map<String, Object> getAllProperties(PropertySource<?> aPropSource) {
Map<String, Object> result = new HashMap<>();
if (aPropSource instanceof CompositePropertySource) {
CompositePropertySource cps = (CompositePropertySource) aPropSource;
cps.getPropertySources().forEach(ps -> addAll(result, getAllProperties(ps)));
return result;
}
if (aPropSource instanceof EnumerablePropertySource<?>) {
EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource;
Arrays.asList(ps.getPropertyNames()).forEach(key -> result.put(key, ps.getProperty(key)));
return result;
}
return result;
}
private static void addAll(Map<String, Object> aBase, Map<String, Object> aToBeAdded) {
for (Map.Entry<String, Object> entry : aToBeAdded.entrySet()) {
if (aBase.containsKey(entry.getKey())) {
continue;
}
aBase.put(entry.getKey(), entry.getValue());
}
}
}

View File

@@ -8,17 +8,14 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory; import ca.uhn.fhir.jpa.subscription.channel.subscription.SubscriptionDeliveryHandlerFactory;
import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender; import ca.uhn.fhir.jpa.subscription.match.deliver.email.IEmailSender;
import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender; import ca.uhn.fhir.jpa.subscription.match.deliver.email.JavaMailEmailSender;
import org.apache.commons.dbcp2.BasicDataSource; import com.google.common.base.Strings;
import org.hl7.fhir.dstu2.model.Subscription; import org.hl7.fhir.dstu2.model.Subscription;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.thymeleaf.util.Validate;
import java.lang.reflect.InvocationTargetException; import java.util.Optional;
import java.sql.Driver;
/** /**
* This is the primary configuration file for the example server * This is the primary configuration file for the example server
@@ -29,57 +26,34 @@ public class FhirServerConfigCommon {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirServerConfigCommon.class);
private Boolean enableIndexMissingFields = HapiProperties.getEnableIndexMissingFields();
private Boolean autoCreatePlaceholderReferenceTargets = HapiProperties.getAutoCreatePlaceholderReferenceTargets();
private Boolean enforceReferentialIntegrityOnWrite = HapiProperties.getEnforceReferentialIntegrityOnWrite();
private Boolean enforceReferentialIntegrityOnDelete = HapiProperties.getEnforceReferentialIntegrityOnDelete();
private Boolean allowContainsSearches = HapiProperties.getAllowContainsSearches();
private Boolean allowMultipleDelete = HapiProperties.getAllowMultipleDelete();
private Boolean allowExternalReferences = HapiProperties.getAllowExternalReferences();
private Boolean expungeEnabled = HapiProperties.getExpungeEnabled();
private Boolean allowPlaceholderReferences = HapiProperties.getAllowPlaceholderReferences();
private Boolean subscriptionRestHookEnabled = HapiProperties.getSubscriptionRestHookEnabled();
private Boolean subscriptionEmailEnabled = HapiProperties.getSubscriptionEmailEnabled();
private Boolean allowOverrideDefaultSearchParams = HapiProperties.getAllowOverrideDefaultSearchParams();
private String emailFrom = HapiProperties.getEmailFrom();
private Boolean emailEnabled = HapiProperties.getEmailEnabled();
private String emailHost = HapiProperties.getEmailHost();
private Integer emailPort = HapiProperties.getEmailPort();
private String emailUsername = HapiProperties.getEmailUsername();
private String emailPassword = HapiProperties.getEmailPassword();
private Boolean emailAuth = HapiProperties.getEmailAuth();
private Boolean emailStartTlsEnable = HapiProperties.getEmailStartTlsEnable();
private Boolean emailStartTlsRequired = HapiProperties.getEmailStartTlsRequired();
private Boolean emailQuitWait = HapiProperties.getEmailQuitWait();
@Autowired
private ApplicationContext myAppCtx;
public FhirServerConfigCommon() { public FhirServerConfigCommon(AppProperties appProperties) {
ourLog.info("Server configured to " + (this.allowContainsSearches ? "allow" : "deny") + " contains searches"); ourLog.info("Server configured to " + (appProperties.getAllow_contains_searches() ? "allow" : "deny") + " contains searches");
ourLog.info("Server configured to " + (this.allowMultipleDelete ? "allow" : "deny") + " multiple deletes"); ourLog.info("Server configured to " + (appProperties.getAllow_multiple_delete() ? "allow" : "deny") + " multiple deletes");
ourLog.info("Server configured to " + (this.allowExternalReferences ? "allow" : "deny") + " external references"); ourLog.info("Server configured to " + (appProperties.getAllow_external_references() ? "allow" : "deny") + " external references");
ourLog.info("Server configured to " + (this.expungeEnabled ? "enable" : "disable") + " expunges"); ourLog.info("Server configured to " + (appProperties.getExpunge_enabled() ? "enable" : "disable") + " expunges");
ourLog.info("Server configured to " + (this.allowPlaceholderReferences ? "allow" : "deny") + " placeholder references"); ourLog.info("Server configured to " + (appProperties.getAllow_placeholder_references() ? "allow" : "deny") + " placeholder references");
ourLog.info("Server configured to " + (this.allowOverrideDefaultSearchParams ? "allow" : "deny") + " overriding default search params"); ourLog.info("Server configured to " + (appProperties.getAllow_override_default_search_params() ? "allow" : "deny") + " overriding default search params");
if (this.emailEnabled) { if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("Server is configured to enable email with host '" + this.emailHost + "' and port " + this.emailPort.toString()); AppProperties.Subscription.Email email = appProperties.getSubscription().getEmail();
ourLog.info("Server will use '" + this.emailFrom + "' as the from email address"); ourLog.info("Server is configured to enable email with host '" + email.getHost() + "' and port " + email.getPort());
ourLog.info("Server will use '" + email.getFrom() + "' as the from email address");
if (this.emailUsername != null && this.emailUsername.length() > 0) { if (!Strings.isNullOrEmpty(email.getUsername())) {
ourLog.info("Server is configured to use username '" + this.emailUsername + "' for email"); ourLog.info("Server is configured to use username '" + email.getUsername() + "' for email");
} }
if (this.emailPassword != null && this.emailPassword.length() > 0) { if (!Strings.isNullOrEmpty(email.getPassword())) {
ourLog.info("Server is configured to use a password for email"); ourLog.info("Server is configured to use a password for email");
} }
} }
if (this.subscriptionRestHookEnabled) { if (appProperties.getSubscription().getResthook_enabled()) {
ourLog.info("REST-hook subscriptions enabled"); ourLog.info("REST-hook subscriptions enabled");
} }
if (this.subscriptionEmailEnabled) { if (appProperties.getSubscription().getEmail() != null) {
ourLog.info("Email subscriptions enabled"); ourLog.info("Email subscriptions enabled");
} }
} }
@@ -88,56 +62,60 @@ public class FhirServerConfigCommon {
* Configure FHIR properties around the the JPA server via this bean * Configure FHIR properties around the the JPA server via this bean
*/ */
@Bean() @Bean()
public DaoConfig daoConfig() { public DaoConfig daoConfig(AppProperties appProperties) {
DaoConfig retVal = new DaoConfig(); DaoConfig retVal = new DaoConfig();
retVal.setIndexMissingFields(this.enableIndexMissingFields ? DaoConfig.IndexEnabledEnum.ENABLED : DaoConfig.IndexEnabledEnum.DISABLED); retVal.setIndexMissingFields(appProperties.getEnable_index_missing_fields() ? DaoConfig.IndexEnabledEnum.ENABLED : DaoConfig.IndexEnabledEnum.DISABLED);
retVal.setAutoCreatePlaceholderReferenceTargets(this.autoCreatePlaceholderReferenceTargets); retVal.setAutoCreatePlaceholderReferenceTargets(appProperties.getAuto_create_placeholder_reference_targets());
retVal.setEnforceReferentialIntegrityOnWrite(this.enforceReferentialIntegrityOnWrite); retVal.setEnforceReferentialIntegrityOnWrite(appProperties.getEnforce_referential_integrity_on_write());
retVal.setEnforceReferentialIntegrityOnDelete(this.enforceReferentialIntegrityOnDelete); retVal.setEnforceReferentialIntegrityOnDelete(appProperties.getEnforce_referential_integrity_on_delete());
retVal.setAllowContainsSearches(this.allowContainsSearches); retVal.setAllowContainsSearches(appProperties.getAllow_contains_searches());
retVal.setAllowMultipleDelete(this.allowMultipleDelete); retVal.setAllowMultipleDelete(appProperties.getAllow_multiple_delete());
retVal.setAllowExternalReferences(this.allowExternalReferences); retVal.setAllowExternalReferences(appProperties.getAllow_external_references());
retVal.setExpungeEnabled(this.expungeEnabled); retVal.setExpungeEnabled(appProperties.getExpunge_enabled());
retVal.setAutoCreatePlaceholderReferenceTargets(this.allowPlaceholderReferences); retVal.setAutoCreatePlaceholderReferenceTargets(appProperties.getAllow_placeholder_references());
retVal.setEmailFromAddress(this.emailFrom); if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null)
retVal.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom());
Integer maxFetchSize = HapiProperties.getMaximumFetchSize(); Integer maxFetchSize = appProperties.getMax_page_size();
retVal.setFetchSizeDefaultMaximum(maxFetchSize); retVal.setFetchSizeDefaultMaximum(maxFetchSize);
ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize)); ourLog.info("Server configured to have a maximum fetch size of " + (maxFetchSize == Integer.MAX_VALUE ? "'unlimited'" : maxFetchSize));
Long reuseCachedSearchResultsMillis = HapiProperties.getReuseCachedSearchResultsMillis(); Long reuseCachedSearchResultsMillis = appProperties.getReuse_cached_search_results_millis();
retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis); retVal.setReuseCachedSearchResultsForMillis(reuseCachedSearchResultsMillis);
ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis); ourLog.info("Server configured to cache search results for {} milliseconds", reuseCachedSearchResultsMillis);
Long retainCachedSearchesMinutes = HapiProperties.getExpireSearchResultsAfterMins();
Long retainCachedSearchesMinutes = appProperties.getRetain_cached_searches_mins();
retVal.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000); retVal.setExpireSearchResultsAfterMillis(retainCachedSearchesMinutes * 60 * 1000);
// Subscriptions are enabled by channel type if(appProperties.getSubscription() != null) {
if (HapiProperties.getSubscriptionRestHookEnabled()) { // Subscriptions are enabled by channel type
ourLog.info("Enabling REST-hook subscriptions"); if (appProperties.getSubscription().getResthook_enabled()) {
retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK); ourLog.info("Enabling REST-hook subscriptions");
} retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.RESTHOOK);
if (HapiProperties.getSubscriptionEmailEnabled()) { }
ourLog.info("Enabling email subscriptions"); if (appProperties.getSubscription().getEmail() != null) {
retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL); ourLog.info("Enabling email subscriptions");
} retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.EMAIL);
if (HapiProperties.getSubscriptionWebsocketEnabled()) { }
ourLog.info("Enabling websocket subscriptions"); if (appProperties.getSubscription().getWebsocket_enabled()) {
retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET); ourLog.info("Enabling websocket subscriptions");
retVal.addSupportedSubscriptionType(org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType.WEBSOCKET);
}
} }
retVal.setFilterParameterEnabled(HapiProperties.getFilterSearchEnabled()); retVal.setFilterParameterEnabled(appProperties.getFilter_search_enabled());
return retVal; return retVal;
} }
@Bean @Bean
public PartitionSettings partitionSettings() { public PartitionSettings partitionSettings(AppProperties appProperties) {
PartitionSettings retVal = new PartitionSettings(); PartitionSettings retVal = new PartitionSettings();
// Partitioning // Partitioning
if (HapiProperties.getPartitioningMultitenancyEnabled()) { if (appProperties.getPartitioning() != null) {
retVal.setPartitioningEnabled(true); retVal.setPartitioningEnabled(true);
} }
@@ -146,19 +124,20 @@ public class FhirServerConfigCommon {
@Bean @Bean
public ModelConfig modelConfig() { public ModelConfig modelConfig(AppProperties appProperties) {
ModelConfig modelConfig = new ModelConfig(); ModelConfig modelConfig = new ModelConfig();
modelConfig.setAllowContainsSearches(this.allowContainsSearches); modelConfig.setAllowContainsSearches(appProperties.getAllow_contains_searches());
modelConfig.setAllowExternalReferences(this.allowExternalReferences); modelConfig.setAllowExternalReferences(appProperties.getAllow_external_references());
modelConfig.setDefaultSearchParamsCanBeOverridden(this.allowOverrideDefaultSearchParams); modelConfig.setDefaultSearchParamsCanBeOverridden(appProperties.getAllow_override_default_search_params());
modelConfig.setEmailFromAddress(this.emailFrom); if(appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null)
modelConfig.setEmailFromAddress(appProperties.getSubscription().getEmail().getFrom());
// You can enable these if you want to support Subscriptions from your server // You can enable these if you want to support Subscriptions from your server
if (this.subscriptionRestHookEnabled) { if (appProperties.getSubscription() != null && appProperties.getSubscription().getResthook_enabled() != null) {
modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK); modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK);
} }
if (this.subscriptionEmailEnabled) { if (appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) {
modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL); modelConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL);
} }
@@ -171,7 +150,7 @@ public class FhirServerConfigCommon {
* <p> * <p>
* A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource. * A URL to a remote database could also be placed here, along with login credentials and other properties supported by BasicDataSource.
*/ */
@Bean(destroyMethod = "close") /*@Bean(destroyMethod = "close")
public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { public BasicDataSource dataSource() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
BasicDataSource retVal = new BasicDataSource(); BasicDataSource retVal = new BasicDataSource();
Driver driver = (Driver) Class.forName(HapiProperties.getDataSourceDriver()).getConstructor().newInstance(); Driver driver = (Driver) Class.forName(HapiProperties.getDataSourceDriver()).getConstructor().newInstance();
@@ -181,38 +160,37 @@ public class FhirServerConfigCommon {
retVal.setPassword(HapiProperties.getDataSourcePassword()); retVal.setPassword(HapiProperties.getDataSourcePassword());
retVal.setMaxTotal(HapiProperties.getDataSourceMaxPoolSize()); retVal.setMaxTotal(HapiProperties.getDataSourceMaxPoolSize());
return retVal; return retVal;
} }*/
@Lazy @Lazy
@Bean @Bean
public IBinaryStorageSvc binaryStorageSvc() { public IBinaryStorageSvc binaryStorageSvc(AppProperties appProperties) {
DatabaseBlobBinaryStorageSvcImpl binaryStorageSvc = new DatabaseBlobBinaryStorageSvcImpl(); DatabaseBlobBinaryStorageSvcImpl binaryStorageSvc = new DatabaseBlobBinaryStorageSvcImpl();
if (HapiProperties.getMaxBinarySize() != null) { if (appProperties.getMax_binary_size() != null) {
binaryStorageSvc.setMaximumBinarySize(HapiProperties.getMaxBinarySize()); binaryStorageSvc.setMaximumBinarySize(appProperties.getMax_binary_size());
} }
return binaryStorageSvc; return binaryStorageSvc;
} }
@Bean() @Bean()
public IEmailSender emailSender() { public IEmailSender emailSender(AppProperties appProperties, Optional<SubscriptionDeliveryHandlerFactory> subscriptionDeliveryHandlerFactory) {
if (this.emailEnabled) { if (appProperties.getSubscription() != null && appProperties.getSubscription().getEmail() != null) {
JavaMailEmailSender retVal = new JavaMailEmailSender(); JavaMailEmailSender retVal = new JavaMailEmailSender();
retVal.setSmtpServerHostname(this.emailHost); AppProperties.Subscription.Email email = appProperties.getSubscription().getEmail();
retVal.setSmtpServerPort(this.emailPort); retVal.setSmtpServerHostname(email.getHost());
retVal.setSmtpServerUsername(this.emailUsername); retVal.setSmtpServerPort(email.getPort());
retVal.setSmtpServerPassword(this.emailPassword); retVal.setSmtpServerUsername(email.getUsername());
retVal.setAuth(this.emailAuth); retVal.setSmtpServerPassword(email.getPassword());
retVal.setStartTlsEnable(this.emailStartTlsEnable); retVal.setAuth(email.getAuth());
retVal.setStartTlsRequired(this.emailStartTlsRequired); retVal.setStartTlsEnable(email.getStartTlsEnable());
retVal.setQuitWait(this.emailQuitWait); retVal.setStartTlsRequired(email.getStartTlsRequired());
retVal.setQuitWait(email.getQuitWait());
SubscriptionDeliveryHandlerFactory subscriptionDeliveryHandlerFactory = myAppCtx.getBean(SubscriptionDeliveryHandlerFactory.class);
Validate.notNull(subscriptionDeliveryHandlerFactory, "No subscription delivery handler");
subscriptionDeliveryHandlerFactory.setEmailSender(retVal);
if(subscriptionDeliveryHandlerFactory.isPresent())
subscriptionDeliveryHandlerFactory.get().setEmailSender(retVal);
return retVal; return retVal;
} }

View File

@@ -7,53 +7,66 @@ import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
@Configuration @Configuration
@Profile("dstu2")
public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 { public class FhirServerConfigDstu2 extends BaseJavaConfigDstu2 {
@Autowired @Autowired
private DataSource myDataSource; private DataSource myDataSource;
/** /**
* We override the paging provider definition so that we can customize * We override the paging provider definition so that we can customize
* the default/max page sizes for search results. You can set these however * the default/max page sizes for search results. You can set these however
* you want, although very large page sizes will require a lot of RAM. * you want, although very large page sizes will require a lot of RAM.
*/ */
@Override @Autowired
public DatabaseBackedPagingProvider databaseBackedPagingProvider() { AppProperties appProperties;
DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize()); @Override
pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize()); public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return pagingProvider; DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
} pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size());
pagingProvider.setMaximumPageSize(appProperties.getMax_page_size());
@Override return pagingProvider;
@Bean() }
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory(); @Autowired
retVal.setPersistenceUnitName("HAPI_PU"); private ConfigurableEnvironment configurableEnvironment;
try { @Override
retVal.setDataSource(myDataSource); @Bean()
} catch (Exception e) { public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e); LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
} retVal.setPersistenceUnitName("HAPI_PU");
retVal.setJpaProperties(HapiProperties.getJpaProperties()); try {
return retVal; retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
} }
retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment));
return retVal;
}
@Bean @Bean
@Primary @Primary
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager(); JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory); retVal.setEntityManagerFactory(entityManagerFactory);
return retVal; return retVal;
} }
} }

View File

@@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@@ -14,46 +16,53 @@ import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
@Configuration @Configuration
@Profile("dstu3")
public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 { public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
@Autowired @Autowired
private DataSource myDataSource; private DataSource myDataSource;
/** /**
* We override the paging provider definition so that we can customize * We override the paging provider definition so that we can customize
* the default/max page sizes for search results. You can set these however * the default/max page sizes for search results. You can set these however
* you want, although very large page sizes will require a lot of RAM. * you want, although very large page sizes will require a lot of RAM.
*/ */
@Override @Autowired
public DatabaseBackedPagingProvider databaseBackedPagingProvider() { AppProperties appProperties;
DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize()); @Override
pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize()); public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return pagingProvider; DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size());
pagingProvider.setMaximumPageSize(appProperties.getMax_page_size());
return pagingProvider;
}
@Autowired
private ConfigurableEnvironment configurableEnvironment;
@Override
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
} }
@Override retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment));
@Bean return retVal;
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { }
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try { @Bean
retVal.setDataSource(myDataSource); @Primary
} catch (Exception e) { public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e); JpaTransactionManager retVal = new JpaTransactionManager();
} retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
retVal.setJpaProperties(HapiProperties.getJpaProperties()); }
return retVal;
}
@Bean
@Primary
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory);
return retVal;
}
} }

View File

@@ -7,53 +7,65 @@ import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Properties;
@Configuration @Configuration
@Profile("r4")
public class FhirServerConfigR4 extends BaseJavaConfigR4 { public class FhirServerConfigR4 extends BaseJavaConfigR4 {
@Autowired @Autowired
private DataSource myDataSource; private DataSource myDataSource;
/** /**
* We override the paging provider definition so that we can customize * We override the paging provider definition so that we can customize
* the default/max page sizes for search results. You can set these however * the default/max page sizes for search results. You can set these however
* you want, although very large page sizes will require a lot of RAM. * you want, although very large page sizes will require a lot of RAM.
*/ */
@Override @Autowired
public DatabaseBackedPagingProvider databaseBackedPagingProvider() { AppProperties appProperties;
DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize()); @Override
pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize()); public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return pagingProvider; DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size());
pagingProvider.setMaximumPageSize(appProperties.getMax_page_size());
return pagingProvider;
}
@Autowired
private ConfigurableEnvironment configurableEnvironment;
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
} }
@Override retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment));
@Bean() return retVal;
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { }
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
}
retVal.setJpaProperties(HapiProperties.getJpaProperties());
return retVal;
}
@Bean @Bean
@Primary @Primary
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager(); JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory); retVal.setEntityManagerFactory(entityManagerFactory);
return retVal; return retVal;
} }
} }

View File

@@ -7,6 +7,8 @@ import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
@@ -14,46 +16,53 @@ import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource; import javax.sql.DataSource;
@Configuration @Configuration
@Profile("r5")
public class FhirServerConfigR5 extends BaseJavaConfigR5 { public class FhirServerConfigR5 extends BaseJavaConfigR5 {
@Autowired @Autowired
private DataSource myDataSource; private DataSource myDataSource;
/** /**
* We override the paging provider definition so that we can customize * We override the paging provider definition so that we can customize
* the default/max page sizes for search results. You can set these however * the default/max page sizes for search results. You can set these however
* you want, although very large page sizes will require a lot of RAM. * you want, although very large page sizes will require a lot of RAM.
*/ */
@Override @Autowired
public DatabaseBackedPagingProvider databaseBackedPagingProvider() { AppProperties appProperties;
DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(HapiProperties.getDefaultPageSize()); @Override
pagingProvider.setMaximumPageSize(HapiProperties.getMaximumPageSize()); public DatabaseBackedPagingProvider databaseBackedPagingProvider() {
return pagingProvider; DatabaseBackedPagingProvider pagingProvider = super.databaseBackedPagingProvider();
pagingProvider.setDefaultPageSize(appProperties.getDefault_page_size());
pagingProvider.setMaximumPageSize(appProperties.getMax_page_size());
return pagingProvider;
}
@Autowired
private ConfigurableEnvironment configurableEnvironment;
@Override
@Bean()
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
} }
@Override retVal.setJpaProperties(EnvironmentHelper.getHibernateProperties(configurableEnvironment));
@Bean() return retVal;
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { }
LocalContainerEntityManagerFactoryBean retVal = super.entityManagerFactory();
retVal.setPersistenceUnitName("HAPI_PU");
try {
retVal.setDataSource(myDataSource);
} catch (Exception e) {
throw new ConfigurationException("Could not set the data source due to a configuration issue", e);
}
retVal.setJpaProperties(HapiProperties.getJpaProperties());
return retVal;
}
@Bean @Bean
@Primary @Primary
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) { public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager retVal = new JpaTransactionManager(); JpaTransactionManager retVal = new JpaTransactionManager();
retVal.setEntityManagerFactory(entityManagerFactory); retVal.setEntityManagerFactory(entityManagerFactory);
return retVal; return retVal;
} }
} }

View File

@@ -1,11 +1,10 @@
package ca.uhn.fhir.jpa.starter; package ca.uhn.fhir.jpa.starter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import ca.uhn.fhir.to.FhirTesterMvcConfig; import ca.uhn.fhir.to.FhirTesterMvcConfig;
import ca.uhn.fhir.to.TesterConfig; import ca.uhn.fhir.to.TesterConfig;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
//@formatter:off //@formatter:off
/** /**
@@ -34,18 +33,22 @@ public class FhirTesterConfig {
* deploying your server to a place with a fully qualified domain name, * deploying your server to a place with a fully qualified domain name,
* you might want to use that instead of using the variable. * you might want to use that instead of using the variable.
*/ */
@Bean @Bean
public TesterConfig testerConfig() { public TesterConfig testerConfig(AppProperties appProperties) {
TesterConfig retVal = new TesterConfig(); TesterConfig retVal = new TesterConfig();
retVal appProperties.getTester().stream().forEach(t -> {
.addServer() retVal
.withId(HapiProperties.getServerId()) .addServer()
.withFhirVersion(HapiProperties.getFhirVersion()) .withId(t.getId())
.withBaseUrl(HapiProperties.getServerAddress()) .withFhirVersion(t.getFhir_version())
.withName(HapiProperties.getServerName()); .withBaseUrl(t.getServer_address())
retVal.setRefuseToFetchThirdPartyUrls(HapiProperties.getTesterConfigRefustToFetchThirdPartyUrls()); .withName(t.getName());
return retVal; retVal.setRefuseToFetchThirdPartyUrls(
} t.getRefuse_to_fetch_third_party_urls());
});
return retVal;
}
} }
//@formatter:on //@formatter:on

View File

@@ -1,535 +0,0 @@
package ca.uhn.fhir.jpa.starter;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.jpa.api.config.DaoConfig;
import ca.uhn.fhir.jpa.search.elastic.ElasticsearchHibernatePropertiesBuilder;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.search.elasticsearch.cfg.ElasticsearchIndexStatus;
import org.hibernate.search.elasticsearch.cfg.IndexSchemaManagementStrategy;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.Map;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.commons.lang3.StringUtils.trim;
public class HapiProperties {
static final String ENABLE_INDEX_MISSING_FIELDS = "enable_index_missing_fields";
static final String AUTO_CREATE_PLACEHOLDER_REFERENCE_TARGETS = "auto_create_placeholder_reference_targets";
static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE = "enforce_referential_integrity_on_write";
static final String ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE = "enforce_referential_integrity_on_delete";
static final String BINARY_STORAGE_ENABLED = "binary_storage.enabled";
static final String ALLOW_EXTERNAL_REFERENCES = "allow_external_references";
static final String ALLOW_MULTIPLE_DELETE = "allow_multiple_delete";
static final String ALLOW_PLACEHOLDER_REFERENCES = "allow_placeholder_references";
static final String REUSE_CACHED_SEARCH_RESULTS_MILLIS = "reuse_cached_search_results_millis";
static final String DATASOURCE_DRIVER = "datasource.driver";
static final String DATASOURCE_MAX_POOL_SIZE = "datasource.max_pool_size";
static final String DATASOURCE_PASSWORD = "datasource.password";
static final String DATASOURCE_URL = "datasource.url";
static final String DATASOURCE_USERNAME = "datasource.username";
static final String DEFAULT_ENCODING = "default_encoding";
static final String DEFAULT_PAGE_SIZE = "default_page_size";
static final String DEFAULT_PRETTY_PRINT = "default_pretty_print";
static final String ETAG_SUPPORT = "etag_support";
static final String FHIR_VERSION = "fhir_version";
static final String ALLOW_CASCADING_DELETES = "allow_cascading_deletes";
static final String HAPI_PROPERTIES = "hapi.properties";
static final String LOGGER_ERROR_FORMAT = "logger.error_format";
static final String LOGGER_FORMAT = "logger.format";
static final String LOGGER_LOG_EXCEPTIONS = "logger.log_exceptions";
static final String LOGGER_NAME = "logger.name";
static final String MAX_FETCH_SIZE = "max_fetch_size";
static final String MAX_PAGE_SIZE = "max_page_size";
static final String SERVER_ADDRESS = "server_address";
static final String SERVER_ID = "server.id";
static final String SERVER_NAME = "server.name";
static final String SUBSCRIPTION_EMAIL_ENABLED = "subscription.email.enabled";
static final String SUBSCRIPTION_RESTHOOK_ENABLED = "subscription.resthook.enabled";
static final String SUBSCRIPTION_WEBSOCKET_ENABLED = "subscription.websocket.enabled";
static final String EMPI_ENABLED = "empi.enabled";
static final String PARTITIONING_ENABLED = "partitioning.enabled";
static final String PARTITIONING_CROSS_PARTITION_REFERENCE_MODE = "partitioning.cross_partition_reference_mode";
static final String ALLOWED_BUNDLE_TYPES = "allowed_bundle_types";
static final String TEST_PORT = "test.port";
static final String TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS = "tester.config.refuse_to_fetch_third_party_urls";
static final String CORS_ENABLED = "cors.enabled";
static final String CORS_ALLOWED_ORIGIN = "cors.allowed_origin";
static final String CORS_ALLOW_CREDENTIALS = "cors.allowCredentials";
static final String ALLOW_CONTAINS_SEARCHES = "allow_contains_searches";
static final String ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS = "allow_override_default_search_params";
static final String EMAIL_FROM = "email.from";
static final String VALIDATE_REQUESTS_ENABLED = "validation.requests.enabled";
static final String VALIDATE_RESPONSES_ENABLED = "validation.responses.enabled";
static final String FILTER_SEARCH_ENABLED = "filter_search.enabled";
static final String GRAPHQL_ENABLED = "graphql.enabled";
static final String BULK_EXPORT_ENABLED = "bulk.export.enabled";
static final String EXPIRE_SEARCH_RESULTS_AFTER_MINS = "retain_cached_searches_mins";
static final String MAX_BINARY_SIZE = "max_binary_size";
static final String PARTITIONING_MULTITENANCY_ENABLED = "partitioning.multitenancy.enabled";
private static final String PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES = "partitioning.partitioning_include_in_search_hashes";
static final String CLIENT_ID_STRATEGY = "daoconfig.client_id_strategy";
private static Properties ourProperties;
public static boolean isElasticSearchEnabled() {
return HapiProperties.getPropertyBoolean("elasticsearch.enabled", false);
}
/*
* Force the configuration to be reloaded
*/
public static void forceReload() {
ourProperties = null;
getProperties();
}
/**
* This is mostly here for unit tests. Use the actual properties file
* to set values
*/
@VisibleForTesting
public static void setProperty(String theKey, String theValue) {
getProperties().setProperty(theKey, theValue);
}
public static Properties getJpaProperties() {
Properties retVal = loadProperties();
if (isElasticSearchEnabled()) {
ElasticsearchHibernatePropertiesBuilder builder = new ElasticsearchHibernatePropertiesBuilder();
builder.setRequiredIndexStatus(getPropertyEnum("elasticsearch.required_index_status", ElasticsearchIndexStatus.class, ElasticsearchIndexStatus.YELLOW));
builder.setRestUrl(getProperty("elasticsearch.rest_url"));
builder.setUsername(getProperty("elasticsearch.username"));
builder.setPassword(getProperty("elasticsearch.password"));
builder.setIndexSchemaManagementStrategy(getPropertyEnum("elasticsearch.schema_management_strategy", IndexSchemaManagementStrategy.class, IndexSchemaManagementStrategy.CREATE));
builder.setDebugRefreshAfterWrite(getPropertyBoolean("elasticsearch.debug.refresh_after_write", false));
builder.setDebugPrettyPrintJsonLog(getPropertyBoolean("elasticsearch.debug.pretty_print_json_log", false));
builder.apply(retVal);
}
return retVal;
}
private static Properties getProperties() {
if (ourProperties == null) {
Properties properties = loadProperties();
HapiProperties.ourProperties = properties;
}
return ourProperties;
}
@NotNull
private static Properties loadProperties() {
// Load the configurable properties file
Properties properties;
try (InputStream in = HapiProperties.class.getClassLoader().getResourceAsStream(HAPI_PROPERTIES)) {
properties = new Properties();
properties.load(in);
} catch (Exception e) {
throw new ConfigurationException("Could not load HAPI properties", e);
}
Properties overrideProps = loadOverrideProperties();
if (overrideProps != null) {
properties.putAll(overrideProps);
}
properties.putAll(System.getenv().entrySet()
.stream()
.filter(e -> e.getValue() != null && properties.containsKey(e.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
return properties;
}
/**
* If a configuration file path is explicitly specified via -Dhapi.properties=<path>, the properties there will
* be used to override the entries in the default hapi.properties file (currently under WEB-INF/classes)
*
* @return properties loaded from the explicitly specified configuraiton file if there is one, or null otherwise.
*/
private static Properties loadOverrideProperties() {
String confFile = System.getProperty(HAPI_PROPERTIES);
if (confFile != null) {
try {
Properties props = new Properties();
props.load(new FileInputStream(confFile));
return props;
} catch (Exception e) {
throw new ConfigurationException("Could not load HAPI properties file: " + confFile, e);
}
}
return null;
}
private static String getProperty(String propertyName) {
String env = "HAPI_" + propertyName.toUpperCase(Locale.US);
env = env.replace(".", "_");
env = env.replace("-", "_");
String propertyValue = System.getenv(env);
if (propertyValue != null) {
return propertyValue;
}
Properties properties = HapiProperties.getProperties();
if (properties != null) {
propertyValue = properties.getProperty(propertyName);
}
return propertyValue;
}
private static String getProperty(String propertyName, String defaultValue) {
String value = getProperty(propertyName);
if (value != null && value.length() > 0) {
return value;
}
return defaultValue;
}
private static Boolean getBooleanProperty(String propertyName, Boolean defaultValue) {
String value = HapiProperties.getProperty(propertyName);
if (value == null || value.length() == 0) {
return defaultValue;
}
return Boolean.parseBoolean(value);
}
private static boolean getBooleanProperty(String propertyName, boolean defaultValue) {
return getBooleanProperty(propertyName, Boolean.valueOf(defaultValue));
}
private static Integer getIntegerProperty(String propertyName, Integer defaultValue) {
String value = HapiProperties.getProperty(propertyName);
if (value == null || value.length() == 0) {
return defaultValue;
}
return Integer.parseInt(value);
}
public static FhirVersionEnum getFhirVersion() {
String fhirVersionString = HapiProperties.getProperty(FHIR_VERSION);
if (fhirVersionString != null && fhirVersionString.length() > 0) {
return FhirVersionEnum.valueOf(fhirVersionString);
}
return FhirVersionEnum.DSTU3;
}
public static boolean isBinaryStorageEnabled() {
return HapiProperties.getBooleanProperty(BINARY_STORAGE_ENABLED, true);
}
public static ETagSupportEnum getEtagSupport() {
String etagSupportString = HapiProperties.getProperty(ETAG_SUPPORT);
if (etagSupportString != null && etagSupportString.length() > 0) {
return ETagSupportEnum.valueOf(etagSupportString);
}
return ETagSupportEnum.ENABLED;
}
public static DaoConfig.ClientIdStrategyEnum getClientIdStrategy() {
String idStrategy = HapiProperties.getProperty(CLIENT_ID_STRATEGY);
if (idStrategy != null && idStrategy.length() > 0) {
return DaoConfig.ClientIdStrategyEnum.valueOf(idStrategy);
}
return DaoConfig.ClientIdStrategyEnum.ALPHANUMERIC;
}
public static EncodingEnum getDefaultEncoding() {
String defaultEncodingString = HapiProperties.getProperty(DEFAULT_ENCODING);
if (defaultEncodingString != null && defaultEncodingString.length() > 0) {
return EncodingEnum.valueOf(defaultEncodingString);
}
return EncodingEnum.JSON;
}
public static Boolean getDefaultPrettyPrint() {
return HapiProperties.getBooleanProperty(DEFAULT_PRETTY_PRINT, true);
}
public static String getServerAddress() {
return HapiProperties.getProperty(SERVER_ADDRESS);
}
public static Integer getDefaultPageSize() {
return HapiProperties.getIntegerProperty(DEFAULT_PAGE_SIZE, 20);
}
public static Integer getMaximumPageSize() {
return HapiProperties.getIntegerProperty(MAX_PAGE_SIZE, 200);
}
public static Integer getMaximumFetchSize() {
return HapiProperties.getIntegerProperty(MAX_FETCH_SIZE, Integer.MAX_VALUE);
}
public static String getLoggerName() {
return HapiProperties.getProperty(LOGGER_NAME, "fhirtest.access");
}
public static String getLoggerFormat() {
return HapiProperties.getProperty(LOGGER_FORMAT, "Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]");
}
public static String getLoggerErrorFormat() {
return HapiProperties.getProperty(LOGGER_ERROR_FORMAT, "ERROR - ${requestVerb} ${requestUrl}");
}
public static Boolean getLoggerLogExceptions() {
return HapiProperties.getBooleanProperty(LOGGER_LOG_EXCEPTIONS, true);
}
public static String getDataSourceDriver() {
return HapiProperties.getProperty(DATASOURCE_DRIVER, "org.apache.derby.jdbc.EmbeddedDriver");
}
public static Integer getDataSourceMaxPoolSize() {
return HapiProperties.getIntegerProperty(DATASOURCE_MAX_POOL_SIZE, 10);
}
public static String getDataSourceUrl() {
return HapiProperties.getProperty(DATASOURCE_URL, "jdbc:derby:directory:target/jpaserver_derby_files;create=true");
}
public static String getDataSourceUsername() {
return HapiProperties.getProperty(DATASOURCE_USERNAME);
}
public static String getDataSourcePassword() {
return HapiProperties.getProperty(DATASOURCE_PASSWORD);
}
public static Boolean getAllowMultipleDelete() {
return HapiProperties.getBooleanProperty(ALLOW_MULTIPLE_DELETE, false);
}
public static Boolean getAllowCascadingDeletes() {
return HapiProperties.getBooleanProperty(ALLOW_CASCADING_DELETES, false);
}
public static Boolean getAllowExternalReferences() {
return HapiProperties.getBooleanProperty(ALLOW_EXTERNAL_REFERENCES, false);
}
public static Boolean getExpungeEnabled() {
return HapiProperties.getBooleanProperty("expunge_enabled", true);
}
public static Boolean getTesterConfigRefustToFetchThirdPartyUrls() {
return HapiProperties.getBooleanProperty(TESTER_CONFIG_REFUSE_TO_FETCH_THIRD_PARTY_URLS, false);
}
public static Boolean getCorsEnabled() {
return HapiProperties.getBooleanProperty(CORS_ENABLED, true);
}
public static String getCorsAllowedOrigin() {
return HapiProperties.getProperty(CORS_ALLOWED_ORIGIN, "*");
}
public static String getAllowedBundleTypes() {
return HapiProperties.getProperty(ALLOWED_BUNDLE_TYPES, "");
}
@Nonnull
public static Set<String> getSupportedResourceTypes() {
String[] types = defaultString(getProperty("supported_resource_types")).split(",");
return Arrays.stream(types)
.map(StringUtils::trim)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toSet());
}
public static String getServerName() {
return HapiProperties.getProperty(SERVER_NAME, "Local Tester");
}
public static String getServerId() {
return HapiProperties.getProperty(SERVER_ID, "home");
}
public static Boolean getAllowPlaceholderReferences() {
return HapiProperties.getBooleanProperty(ALLOW_PLACEHOLDER_REFERENCES, true);
}
public static Boolean getSubscriptionEmailEnabled() {
return HapiProperties.getBooleanProperty(SUBSCRIPTION_EMAIL_ENABLED, false);
}
public static Boolean getSubscriptionRestHookEnabled() {
return HapiProperties.getBooleanProperty(SUBSCRIPTION_RESTHOOK_ENABLED, false);
}
public static Boolean getSubscriptionWebsocketEnabled() {
return HapiProperties.getBooleanProperty(SUBSCRIPTION_WEBSOCKET_ENABLED, false);
}
public static Boolean getEmpiEnabled() {
return HapiProperties.getBooleanProperty(EMPI_ENABLED, false);
}
public static Boolean getPartitioningEnabled() {
return HapiProperties.getBooleanProperty(PARTITIONING_ENABLED, false);
}
public static String getPartitioningCrossPartitionReferenceMode() {
return HapiProperties.getProperty(PARTITIONING_CROSS_PARTITION_REFERENCE_MODE, "NOT_ALLOWED");
}
public static Boolean getIncludePartitionInSearchHashes() {
return HapiProperties.getBooleanProperty(PARTITIONING_INCLUDE_PARTITION_IN_SEARCH_HASHES, true);
}
public static Boolean getAllowContainsSearches() {
return HapiProperties.getBooleanProperty(ALLOW_CONTAINS_SEARCHES, true);
}
public static Boolean getAllowOverrideDefaultSearchParams() {
return HapiProperties.getBooleanProperty(ALLOW_OVERRIDE_DEFAULT_SEARCH_PARAMS, true);
}
public static String getEmailFrom() {
return HapiProperties.getProperty(EMAIL_FROM, "some@test.com");
}
public static Boolean getEmailEnabled() {
return HapiProperties.getBooleanProperty("email.enabled", false);
}
public static String getEmailHost() {
return HapiProperties.getProperty("email.host");
}
public static Integer getEmailPort() {
return HapiProperties.getIntegerProperty("email.port", 0);
}
public static String getEmailUsername() {
return HapiProperties.getProperty("email.username");
}
public static String getEmailPassword() {
return HapiProperties.getProperty("email.password");
}
// Defaults from https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html
public static Boolean getEmailAuth() {
return HapiProperties.getBooleanProperty("email.auth", false);
}
public static Boolean getEmailStartTlsEnable() {
return HapiProperties.getBooleanProperty("email.starttls.enable", false);
}
public static Boolean getEmailStartTlsRequired() {
return HapiProperties.getBooleanProperty("email.starttls.required", false);
}
public static Boolean getEmailQuitWait() {
return HapiProperties.getBooleanProperty("email.quitwait", true);
}
public static Long getReuseCachedSearchResultsMillis() {
String value = HapiProperties.getProperty(REUSE_CACHED_SEARCH_RESULTS_MILLIS, "60000");
return Long.valueOf(value);
}
public static Long getExpireSearchResultsAfterMins() {
String value = HapiProperties.getProperty(EXPIRE_SEARCH_RESULTS_AFTER_MINS, "60");
return Long.valueOf(value);
}
public static Boolean getCorsAllowedCredentials() {
return HapiProperties.getBooleanProperty(CORS_ALLOW_CREDENTIALS, false);
}
public static boolean getValidateRequestsEnabled() {
return HapiProperties.getBooleanProperty(VALIDATE_REQUESTS_ENABLED, false);
}
public static boolean getValidateResponsesEnabled() {
return HapiProperties.getBooleanProperty(VALIDATE_RESPONSES_ENABLED, false);
}
public static boolean getFilterSearchEnabled() {
return HapiProperties.getBooleanProperty(FILTER_SEARCH_ENABLED, true);
}
public static boolean getGraphqlEnabled() {
return HapiProperties.getBooleanProperty(GRAPHQL_ENABLED, true);
}
public static boolean getEnforceReferentialIntegrityOnDelete() {
return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_DELETE, true);
}
public static boolean getEnforceReferentialIntegrityOnWrite() {
return HapiProperties.getBooleanProperty(ENFORCE_REFERENTIAL_INTEGRITY_ON_WRITE, true);
}
public static boolean getAutoCreatePlaceholderReferenceTargets() {
return HapiProperties.getBooleanProperty(AUTO_CREATE_PLACEHOLDER_REFERENCE_TARGETS, true);
}
public static boolean getEnableIndexMissingFields() {
return HapiProperties.getBooleanProperty(ENABLE_INDEX_MISSING_FIELDS, false);
}
public static Integer getMaxBinarySize() {
return getIntegerProperty(MAX_BINARY_SIZE, null);
}
private static boolean getPropertyBoolean(String thePropertyName, boolean theDefaultValue) {
String value = getProperty(thePropertyName, Boolean.toString(theDefaultValue));
return Boolean.parseBoolean(value);
}
private static <T extends Enum> T getPropertyEnum(String thePropertyName, Class<T> theEnumType, T theDefaultValue) {
String value = getProperty(thePropertyName, theDefaultValue.name());
return (T) Enum.valueOf(theEnumType, value);
}
public static boolean getBulkExportEnabled() {
return HapiProperties.getBooleanProperty(BULK_EXPORT_ENABLED, true);
}
public static boolean isFhirPathFilterInterceptorEnabled() {
return HapiProperties.getBooleanProperty("fhirpath_interceptor.enabled", false);
}
public static boolean getPartitioningMultitenancyEnabled() {
return HapiProperties.getBooleanProperty(PARTITIONING_MULTITENANCY_ENABLED, false);
}
}

View File

@@ -1,11 +1,22 @@
package ca.uhn.fhir.jpa.starter; package ca.uhn.fhir.jpa.starter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@Import(AppProperties.class)
public class JpaRestfulServer extends BaseJpaRestfulServer { public class JpaRestfulServer extends BaseJpaRestfulServer {
@Autowired
AppProperties appProperties;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public JpaRestfulServer() {
super();
}
@Override @Override
protected void initialize() throws ServletException { protected void initialize() throws ServletException {
super.initialize(); super.initialize();

View File

@@ -0,0 +1,133 @@
spring:
datasource:
url: 'jdbc:h2:file:./target/database/h2'
username: sa
password: null
driverClassName: org.h2.Driver
max-active: 15
profiles:
### This is the FHIR version. Choose between, dstu2, dstu3, r4 or r5
active: r4
jpa:
properties:
hibernate.dialect: org.hibernate.dialect.H2Dialect
hibernate.search.model_mapping: ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory
hibernate.format_sql: false
hibernate.show_sql: false
hibernate.hbm2ddl.auto: update
hibernate.jdbc.batch_size: 20
hibernate.cache.use_query_cache: false
hibernate.cache.use_second_level_cache: false
hibernate.cache.use_structured_entries: false
hibernate.cache.use_minimal_puts: false
hibernate.search.default.directory_provider: filesystem
hibernate.search.default.indexBase: target/lucenefiles
hibernate.search.lucene_version: LUCENE_CURRENT
batch:
job:
enabled: false
hapi:
fhir:
defer_indexing_for_codesystems_of_size: 101
# implementationguides:
# -
# url: https://build.fhir.org/ig/hl7dk/dk-medcom/branches/corrections/package.tgz
# name: dk.fhir.ig.medcom-core
# version: 0.8.0
# -
# name: hl7.fhir.uv.ips
# version: 0.3.0
#supported_resource_types:
# - Patient
# - Observation
# allow_cascading_deletes: true
# allow_contains_searches: true
# allow_external_references: true
# allow_multiple_delete: true
# allow_override_default_search_params: true
# allow_placeholder_references: true
# auto_create_placeholder_reference_targets: false
# default_encoding: JSON
# default_pretty_print: true
# default_page_size: 20
# enable_index_missing_fields: false
# enforce_referential_integrity_on_delete: false
# enforce_referential_integrity_on_write: false
# etag_support_enabled: true
# expunge_enabled: true
# daoconfig_client_id_strategy: null
# fhirpath_interceptor_enabled: false
# filter_search_enabled: true
# graphql_enabled: true
#partitioning:
# cross_partition_reference_mode: true
# multitenancy_enabled: true
# partitioning_include_in_search_hashes: true
#cors:
# allow_Credentials: true
# Supports multiple, comma separated allowed origin entries
# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca
# allowed_origin:
# - '*'
# logger:
# error_format: 'ERROR - ${requestVerb} ${requestUrl}'
# format: >-
# Path[${servletPath}] Source[${requestHeader.x-forwarded-for}]
# Operation[${operationType} ${operationName} ${idOrResourceName}]
# UA[${requestHeader.user-agent}] Params[${requestParameters}]
# ResponseEncoding[${responseEncodingNoDefault}]
# log_exceptions: true
# name: fhirtest.access
# max_binary_size: 104857600
# max_page_size: 200
# retain_cached_searches_mins: 60
# reuse_cached_search_results_millis: 60000
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8080/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
-
id: global
name: Global Tester
server_address: "http://hapi.fhir.org/baseR4"
refuse_to_fetch_third_party_urls: false
fhir_version: R4
# validation:
# requests_enabled: true
# responses_enabled: true
# binary_storage_enabled: true
# bulk_export_enabled: true
# partitioning_multitenancy_enabled:
# subscription:
# resthook_enabled: false
# websocket_enabled: false
# email:
# from: some@test.com
# host: google.com
# port:
# username:
# password:
# auth:
# startTlsEnable:
# startTlsRequired:
# quitWait:
#
#elasticsearch:
# debug:
# pretty_print_json_log: false
# refresh_after_write: false
# enabled: false
# password: SomePassword
# required_index_status: YELLOW
# rest_url: 'http://localhost:9200'
# schema_management_strategy: CREATE
# username: SomeUsername

View File

@@ -1,167 +0,0 @@
# Adjust this to set the version of FHIR supported by this server. See
# FhirVersionEnum for a list of available constants. Example values include
# DSTU2, DSTU3, R4.
fhir_version=R4
# This is the address that the FHIR server will report as its own address.
# If this server will be deployed (for example) to an internet accessible
# server, put the DNS name of that server here.
#
# Note that this is also the address that the hapi-fhir-testpage-overlay
# (the web UI similar to the one at http://hapi.fhir.org) will use to
# connect internally to the FHIR server, so this also needs to be a name
# accessible from the server itself.
server_address=http://localhost:8080/hapi-fhir-jpaserver/fhir/
enable_index_missing_fields=false
auto_create_placeholder_reference_targets=false
enforce_referential_integrity_on_write=false
enforce_referential_integrity_on_delete=false
default_encoding=JSON
etag_support=ENABLED
reuse_cached_search_results_millis=60000
retain_cached_searches_mins=60
default_page_size=20
max_page_size=200
allow_override_default_search_params=true
allow_contains_searches=true
allow_multiple_delete=true
allow_external_references=true
allow_cascading_deletes=true
allow_placeholder_references=true
expunge_enabled=true
persistence_unit_name=HAPI_PU
logger.name=fhirtest.access
logger.format=Path[${servletPath}] Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${operationName} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}] ResponseEncoding[${responseEncodingNoDefault}]
logger.error_format=ERROR - ${requestVerb} ${requestUrl}
logger.log_exceptions=true
datasource.driver=org.h2.Driver
datasource.url=jdbc:h2:file:./target/database/h2
datasource.username=
datasource.password=
server.name=Local Tester
server.id=home
test.port=
###################################################
# Binary Storage (104857600 = 100mb)
###################################################
max_binary_size=104857600
###################################################
# Validation
###################################################
# Should all incoming requests be validated
validation.requests.enabled=false
# Should outgoing responses be validated
validation.responses.enabled=false
###################################################
# Search Features
###################################################
filter_search.enabled=true
graphql.enabled=true
# See FhirPathFilterInterceptor
fhirpath_interceptor.enabled=false
###################################################
# Supported Resources
###################################################
# Enable the following property if you want to customize the
# list of resources that is supported by the server (i.e. to
# disable specific resources)
#supported_resource_types=Patient,Observation,Encounter
###################################################
# Database Settings
###################################################
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.search.model_mapping=ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory
hibernate.format_sql=false
hibernate.show_sql=false
hibernate.hbm2ddl.auto=update
hibernate.jdbc.batch_size=20
hibernate.cache.use_query_cache=false
hibernate.cache.use_second_level_cache=false
hibernate.cache.use_structured_entries=false
hibernate.cache.use_minimal_puts=false
hibernate.search.default.directory_provider=filesystem
hibernate.search.default.indexBase=target/lucenefiles
hibernate.search.lucene_version=LUCENE_CURRENT
tester.config.refuse_to_fetch_third_party_urls=false
##################################################
# ElasticSearch
# Note that using ElasticSearch is disabled by
# default and the server will use Lucene instead.
##################################################
elasticsearch.enabled=false
elasticsearch.rest_url=http://localhost:9200
elasticsearch.username=SomeUsername
elasticsearch.password=SomePassword
elasticsearch.required_index_status=YELLOW
elasticsearch.schema_management_strategy=CREATE
# Immediately refresh indexes after every write. This is very bad for
# performance, but can be helpful for testing.
elasticsearch.debug.refresh_after_write=false
elasticsearch.debug.pretty_print_json_log=false
##################################################
# Binary Storage Operations
##################################################
binary_storage.enabled=true
##################################################
# Bulk Data Specification
##################################################
bulk.export.enabled=true
##################################################
# CORS Settings
##################################################
cors.enabled=true
cors.allowCredentials=true
# Supports multiple, comma separated allowed origin entries
# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca
cors.allowed_origin=*
##################################################
# Allowed Bundle Types for persistence (defaults are: COLLECTION,DOCUMENT,MESSAGE)
##################################################
#allowed_bundle_types=COLLECTION,DOCUMENT,MESSAGE,TRANSACTION,TRANSACTIONRESPONSE,BATCH,BATCHRESPONSE,HISTORY,SEARCHSET
##################################################
# Subscriptions
##################################################
# Enable REST Hook Subscription Channel
subscription.resthook.enabled=false
# Enable Email Subscription Channel
subscription.email.enabled=false
email.enabled=false
email.from=some@test.com
email.host=
email.port=0
email.username=
email.password=
# Enable Websocket Subscription Channel
subscription.websocket.enabled=false
###################################################
# EMPI
###################################################
empi.enabled=false
###################################################
# Partitioning And Multitenancy
###################################################
partitioning.enabled=false
partitioning.cross_partition_reference_mode=NOT_ALLOWED
partitioning.partitioning_include_in_search_hashes=true
partitioning.multitenancy.enabled=false
#daoconfig.client_id_strategy=ANY

View File

@@ -9,24 +9,6 @@
</encoder> </encoder>
</appender> </appender>
<appender name="EMPI_TROUBLESHOOTING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"><level>DEBUG</level></filter>
<file>${smile.basedir}/log/empi-troubleshooting.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<fileNamePattern>${smile.basedir}/log/empi-troubleshooting.log.%i.gz</fileNamePattern>
<minIndex>1</minIndex>
<maxIndex>9</maxIndex>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<maxFileSize>5MB</maxFileSize>
</triggeringPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n${log.stackfilter.pattern}</pattern>
</encoder>
</appender>
<logger name="ca.uhn.fhir.log.empi_troubleshooting" level="TRACE">
<appender-ref ref="EMPI_TROUBLESHOOTING"/>
</logger>
<root level="INFO"> <root level="INFO">
<appender-ref ref="STDOUT" /> <appender-ref ref="STDOUT" />

View File

@@ -1,54 +0,0 @@
<web-app
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
metadata-complete="false"
version="3.1">
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextClass</param-name>
<param-value>
ca.uhn.fhir.jpa.starter.ApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
</param-value>
</context-param>
<!-- Servlets -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
ca.uhn.fhir.jpa.starter.FhirTesterConfig
</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>fhirServlet</servlet-name>
<servlet-class>ca.uhn.fhir.jpa.starter.JpaRestfulServer</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fhirServlet</servlet-name>
<url-pattern>/fhir/*</url-pattern>
</servlet-mapping>
</web-app>

View File

@@ -0,0 +1,20 @@
package ca.uhn.fhir.jpa.starter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration;
import org.springframework.boot.web.servlet.ServletComponentScan;
@ServletComponentScan(basePackageClasses = {JpaRestfulServer.class})
@SpringBootApplication(exclude = ElasticsearchRestClientAutoConfiguration.class)
public class Demo {
public static void main(String[] args) {
System.setProperty("spring.profiles.active", "r4");
System.setProperty("spring.batch.job.enabled", "false");
SpringApplication.run(Demo.class, args);
//Server is now accessible at eg. http://localhost:8080/metadata
}
}

View File

@@ -5,79 +5,53 @@ import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.nio.file.Paths; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
{
"spring.batch.job.enabled=false",
"spring.profiles.active=dstu2",
"spring.datasource.url=jdbc:h2:mem:dbr2"
})
public class ExampleServerDstu2IT { public class ExampleServerDstu2IT {
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 static IGenericClient ourClient; private IGenericClient ourClient;
private static FhirContext ourCtx; private FhirContext ourCtx;
private static int ourPort;
private static Server ourServer;
static { @LocalServerPort
HapiProperties.forceReload(); private int port;
HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU2");
HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr2"); @Test
ourCtx = FhirContext.forDstu2(); void testCreateAndRead() {
}
@Test
public void testCreateAndRead() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
String methodName = "testCreateResourceConditional"; String methodName = "testCreateResourceConditional";
Patient pt = new Patient(); Patient pt = new Patient();
pt.addName().addFamily(methodName); pt.addName().addFamily(methodName);
IIdType id = ourClient.create().resource(pt).execute().getId(); IIdType id = ourClient.create().resource(pt).execute().getId();
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
assertEquals(methodName, pt2.getName().get(0).getFamily().get(0).getValue()); assertEquals(methodName, pt2.getName().get(0).getFamily().get(0).getValue());
} }
@AfterAll
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeAll @BeforeEach
public static void beforeClass() throws Exception { void beforeEach() {
String path = Paths.get("").toAbsolutePath().toString();
ourLog.info("Project base path is: {}", path);
ourServer = new Server(0);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/hapi-fhir-jpaserver");
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter");
webAppContext.setParentLoaderPriority(true);
ourServer.setHandler(webAppContext);
ourServer.start();
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourCtx = FhirContext.forDstu2();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true)); ourClient.registerInterceptor(new LoggingInterceptor(true));
} }
public static void main(String[] theArgs) throws Exception {
ourPort = 8080;
beforeClass();
}
} }

View File

@@ -7,9 +7,6 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
@@ -18,39 +15,44 @@ import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.dstu3.model.Subscription;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.net.URI; import java.net.URI;
import java.nio.file.Paths;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static ca.uhn.fhir.util.TestUtil.waitForSize; import static ca.uhn.fhir.util.TestUtil.waitForSize;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
{
"spring.batch.job.enabled=false",
"spring.profiles.active=dstu3",
"spring.datasource.url=jdbc:h2:mem:dbr3",
"hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.allow_external_references=true",
"hapi.fhir.allow_placeholder_references=true",
})
public class ExampleServerDstu3IT { public class ExampleServerDstu3IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu3IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
private static IGenericClient ourClient; private IGenericClient ourClient;
private static FhirContext ourCtx; private FhirContext ourCtx;
private static int ourPort;
private static Server ourServer;
static { @LocalServerPort
HapiProperties.forceReload(); private int port;
HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "DSTU3");
HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr3");
HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true");
HapiProperties.setProperty(HapiProperties.ALLOW_EXTERNAL_REFERENCES, "true");
HapiProperties.setProperty(HapiProperties.ALLOW_PLACEHOLDER_REFERENCES, "true");
ourCtx = FhirContext.forDstu3();
}
@Test @Test
public void testCreateAndRead() { public void testCreateAndRead() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
String methodName = "testCreateResourceConditional"; String methodName = "testCreateResourceConditional";
Patient pt = new Patient(); Patient pt = new Patient();
@@ -90,7 +92,7 @@ public class ExampleServerDstu3IT {
SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON);
myWebSocketClient.start(); myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); URI echoUri = new URI("ws://localhost:" + port + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest(); ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri); ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
@@ -119,39 +121,15 @@ public class ExampleServerDstu3IT {
ourClient.delete().resourceById(mySubscriptionId).execute(); ourClient.delete().resourceById(mySubscriptionId).execute();
} }
@AfterAll @BeforeEach
public static void afterClass() throws Exception { void beforeEach() {
ourServer.stop();
}
@BeforeAll
public static void beforeClass() throws Exception {
String path = Paths.get("").toAbsolutePath().toString();
ourLog.info("Project base path is: {}", path);
ourServer = new Server(0);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/hapi-fhir-jpaserver");
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter");
webAppContext.setParentLoaderPriority(true);
ourServer.setHandler(webAppContext);
ourServer.start();
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourCtx = FhirContext.forDstu3();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true)); ourClient.registerInterceptor(new LoggingInterceptor(true));
} }
public static void main(String[] theArgs) throws Exception {
ourPort = 8080;
beforeClass();
}
} }

View File

@@ -7,26 +7,20 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.BundleUtil;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
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.*;
import org.hl7.fhir.r4.model.Observation; import org.junit.jupiter.api.BeforeEach;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Person;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Subscription;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.net.URI; import java.net.URI;
import java.nio.file.Paths;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@@ -38,54 +32,58 @@ import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
{
"spring.batch.job.enabled=false",
"spring.profiles.active=r4",
"spring.datasource.url=jdbc:h2:mem:dbr4",
"hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.empi_enabled=true",
//Override is currently required when using Empi as the construction of the Empi beans are ambiguous as they are constructed multiple places. This is evident when running in a spring boot environment
"spring.main.allow-bean-definition-overriding=true"
})
public class ExampleServerR4IT { public class ExampleServerR4IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
private static IGenericClient ourClient; private IGenericClient ourClient;
private static FhirContext ourCtx; private FhirContext ourCtx;
private static int ourPort;
private static Server ourServer;
static { @LocalServerPort
HapiProperties.forceReload(); private int port;
HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4");
HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4");
HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true");
HapiProperties.setProperty(HapiProperties.EMPI_ENABLED, "true");
ourCtx = FhirContext.forR4();
}
@Test
public void testCreateAndRead() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
String methodName = "testCreateResourceConditional";
Patient pt = new Patient(); @Test
pt.setActive(true); void testCreateAndRead() {
pt.getBirthDateElement().setValueAsString("2020-01-01");
pt.addIdentifier().setSystem("http://foo").setValue("12345");
pt.addName().setFamily(methodName);
IIdType id = ourClient.create().resource(pt).execute().getId();
Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); String methodName = "testCreateResourceConditional";
assertEquals(methodName, pt2.getName().get(0).getFamily());
// Test EMPI Patient pt = new Patient();
pt.setActive(true);
pt.getBirthDateElement().setValueAsString("2020-01-01");
pt.addIdentifier().setSystem("http://foo").setValue("12345");
pt.addName().setFamily(methodName);
IIdType id = ourClient.create().resource(pt).execute().getId();
// Wait until the EMPI message has been processed Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute();
await().until(() -> getPeople().size() > 0); assertEquals(methodName, pt2.getName().get(0).getFamily());
List<Person> persons = getPeople();
// Verify a Person was created that links to our Patient // Test EMPI
Optional<String> personLinkToCreatedPatient = persons.stream()
.map(Person::getLink) // Wait until the EMPI message has been processed
.flatMap(Collection::stream) await().until(() -> getPeople().size() > 0);
.map(Person.PersonLinkComponent::getTarget) List<Person> persons = getPeople();
.map(Reference::getReference)
.filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid)) // Verify a Person was created that links to our Patient
.findAny(); Optional<String> personLinkToCreatedPatient = persons.stream()
assertTrue(personLinkToCreatedPatient.isPresent()); .map(Person::getLink)
} .flatMap(Collection::stream)
.map(Person.PersonLinkComponent::getTarget)
.map(Reference::getReference)
.filter(pid -> id.toUnqualifiedVersionless().getValue().equals(pid))
.findAny();
assertTrue(personLinkToCreatedPatient.isPresent());
}
private List<Person> getPeople() { private List<Person> getPeople() {
Bundle bundle = ourClient.search().forResource(Person.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute(); Bundle bundle = ourClient.search().forResource(Person.class).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute();
@@ -93,103 +91,76 @@ public class ExampleServerR4IT {
} }
@Test @Test
public void testWebsocketSubscription() throws Exception { public void testWebsocketSubscription() throws Exception {
/* /*
* Create subscription * Create subscription
*/ */
Subscription subscription = new Subscription(); Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED); subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
subscription.setCriteria("Observation?status=final"); subscription.setCriteria("Observation?status=final");
Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent(); Subscription.SubscriptionChannelComponent channel = new Subscription.SubscriptionChannelComponent();
channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET); channel.setType(Subscription.SubscriptionChannelType.WEBSOCKET);
channel.setPayload("application/json"); channel.setPayload("application/json");
subscription.setChannel(channel); subscription.setChannel(channel);
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute(); MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
IIdType mySubscriptionId = methodOutcome.getId(); IIdType mySubscriptionId = methodOutcome.getId();
// Wait for the subscription to be activated // Wait for the subscription to be activated
await().until(() -> activeSubscriptionCount() == 3); await().until(() -> activeSubscriptionCount() == 3);
/* /*
* Attach websocket * Attach websocket
*/ */
WebSocketClient myWebSocketClient = new WebSocketClient(); WebSocketClient myWebSocketClient = new WebSocketClient();
SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON);
myWebSocketClient.start(); myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); URI echoUri = new URI("ws://localhost:" + port + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest(); ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri); ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
Session session = connection.get(2, TimeUnit.SECONDS); Session session = connection.get(2, TimeUnit.SECONDS);
ourLog.info("Connected to WS: {}", session.isOpen()); ourLog.info("Connected to WS: {}", session.isOpen());
/* /*
* Create a matching resource * Create a matching resource
*/ */
Observation obs = new Observation(); Observation obs = new Observation();
obs.setStatus(Observation.ObservationStatus.FINAL); obs.setStatus(Observation.ObservationStatus.FINAL);
ourClient.create().resource(obs).execute(); ourClient.create().resource(obs).execute();
// Give some time for the subscription to deliver // Give some time for the subscription to deliver
Thread.sleep(2000); Thread.sleep(2000);
/* /*
* Ensure that we receive a ping on the websocket * Ensure that we receive a ping on the websocket
*/ */
waitForSize(1, () -> mySocketImplementation.myPingCount); waitForSize(1, () -> mySocketImplementation.myPingCount);
/* /*
* Clean up * Clean up
*/ */
ourClient.delete().resourceById(mySubscriptionId).execute(); ourClient.delete().resourceById(mySubscriptionId).execute();
} }
private int activeSubscriptionCount() { private int activeSubscriptionCount() {
return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size(); return ourClient.search().forResource(Subscription.class).where(Subscription.STATUS.exactly().code("active")).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute().getEntry().size();
} }
@AfterAll
public static void afterClass() throws Exception {
ourServer.stop();
}
@BeforeAll @BeforeEach
public static void beforeClass() throws Exception { void beforeEach() {
String path = Paths.get("").toAbsolutePath().toString();
ourLog.info("Project base path is: {}", path); ourCtx = FhirContext.forR4();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourServer = new Server(0); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + port + "/fhir/";
WebAppContext webAppContext = new WebAppContext(); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
webAppContext.setContextPath("/hapi-fhir-jpaserver"); ourClient.registerInterceptor(new LoggingInterceptor(true));
webAppContext.setDisplayName("HAPI FHIR"); }
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter");
webAppContext.setParentLoaderPriority(true);
ourServer.setHandler(webAppContext);
ourServer.start();
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = HapiProperties.getServerAddress();
ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
}
public static void main(String[] theArgs) throws Exception {
ourPort = 8080;
beforeClass();
}
} }

View File

@@ -1,5 +1,9 @@
package ca.uhn.fhir.jpa.starter; package ca.uhn.fhir.jpa.starter;
import static ca.uhn.fhir.util.TestUtil.waitForSize;
import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.EncodingEnum;
@@ -7,9 +11,9 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.test.utilities.JettyUtil; import java.net.URI;
import org.eclipse.jetty.server.Server; import java.util.concurrent.Future;
import org.eclipse.jetty.webapp.WebAppContext; import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient; import org.eclipse.jetty.websocket.client.WebSocketClient;
@@ -20,38 +24,34 @@ import org.hl7.fhir.r5.model.Observation;
import org.hl7.fhir.r5.model.Patient; import org.hl7.fhir.r5.model.Patient;
import org.hl7.fhir.r5.model.Subscription; import org.hl7.fhir.r5.model.Subscription;
import org.hl7.fhir.r5.model.SubscriptionTopic; import org.hl7.fhir.r5.model.SubscriptionTopic;
import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import java.net.URI; @ExtendWith(SpringExtension.class)
import java.nio.file.Paths; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
import java.util.concurrent.Future; {
import java.util.concurrent.TimeUnit; "spring.batch.job.enabled=false",
"spring.profiles.active=r5",
import static ca.uhn.fhir.util.TestUtil.waitForSize; "spring.datasource.url=jdbc:h2:mem:dbr5",
import static org.awaitility.Awaitility.await; "hapi.fhir.subscription.websocket_enabled=true"
import static org.junit.jupiter.api.Assertions.assertEquals; })
public class ExampleServerR5IT { public class ExampleServerR5IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR5IT.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
private static IGenericClient ourClient; private IGenericClient ourClient;
private static FhirContext ourCtx; private FhirContext ourCtx;
private static int ourPort;
private static Server ourServer; @LocalServerPort
private int port;
static {
HapiProperties.forceReload();
HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr5");
HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R5");
HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true");
ourCtx = FhirContext.forR5();
}
@Test @Test
public void testCreateAndRead() { public void testCreateAndRead() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
String methodName = "testCreateResourceConditional"; String methodName = "testCreateResourceConditional";
Patient pt = new Patient(); Patient pt = new Patient();
@@ -97,7 +97,7 @@ public class ExampleServerR5IT {
SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON); SocketImplementation mySocketImplementation = new SocketImplementation(mySubscriptionId.getIdPart(), EncodingEnum.JSON);
myWebSocketClient.start(); myWebSocketClient.start();
URI echoUri = new URI("ws://localhost:" + ourPort + "/hapi-fhir-jpaserver/websocket"); URI echoUri = new URI("ws://localhost:" + port + "/websocket");
ClientUpgradeRequest request = new ClientUpgradeRequest(); ClientUpgradeRequest request = new ClientUpgradeRequest();
ourLog.info("Connecting to : {}", echoUri); ourLog.info("Connecting to : {}", echoUri);
Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request); Future<Session> connection = myWebSocketClient.connect(mySocketImplementation, echoUri, request);
@@ -115,7 +115,7 @@ public class ExampleServerR5IT {
/* /*
* Ensure that we receive a ping on the websocket * Ensure that we receive a ping on the websocket
*/ */
await().until(()->mySocketImplementation.myPingCount > 0); await().until(() -> mySocketImplementation.myPingCount > 0);
/* /*
* Clean up * Clean up
@@ -123,41 +123,14 @@ public class ExampleServerR5IT {
ourClient.delete().resourceById(mySubscriptionId).execute(); ourClient.delete().resourceById(mySubscriptionId).execute();
} }
@AfterAll @BeforeEach
public static void afterClass() throws Exception { void beforeEach() {
ourServer.stop();
}
@BeforeAll
public static void beforeClass() throws Exception {
String path = Paths.get("").toAbsolutePath().toString();
ourLog.info("Project base path is: {}", path);
ourServer = new Server(0);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/hapi-fhir-jpaserver");
webAppContext.setDisplayName("HAPI FHIR");
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter");
webAppContext.setParentLoaderPriority(true);
ourServer.setHandler(webAppContext);
ourServer.start();
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourCtx = FhirContext.forR5();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/"; String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase); ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true)); ourClient.registerInterceptor(new LoggingInterceptor(true));
} }
public static void main(String[] theArgs) throws Exception {
ourPort = 8080;
beforeClass();
}
} }

View File

@@ -7,44 +7,42 @@ import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor; import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor;
import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.rest.server.provider.ProviderConstants;
import ca.uhn.fhir.test.utilities.JettyUtil; import org.hl7.fhir.r4.model.*;
import org.eclipse.jetty.server.Server; import org.junit.jupiter.api.BeforeEach;
import org.eclipse.jetty.webapp.WebAppContext;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.CodeType;
import org.hl7.fhir.r4.model.IntegerType;
import org.hl7.fhir.r4.model.Parameters;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.nio.file.Paths; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class, properties =
{
"spring.batch.job.enabled=false",
"spring.profiles.active=r4",
"spring.datasource.url=jdbc:h2:mem:dbr4-mt",
"hapi.fhir.subscription.websocket_enabled=true",
"hapi.fhir.partitioning.partitioning_include_in_search_hashes=false"
})
public class MultitenantServerR4IT { public class MultitenantServerR4IT {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(MultitenantServerR4IT.class);
private static IGenericClient ourClient; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class);
private static FhirContext ourCtx; private IGenericClient ourClient;
private static int ourPort; private FhirContext ourCtx;
private static Server ourServer;
@LocalServerPort
private int port;
private static UrlTenantSelectionInterceptor ourClientTenantInterceptor; private static UrlTenantSelectionInterceptor ourClientTenantInterceptor;
static {
HapiProperties.forceReload();
HapiProperties.setProperty(HapiProperties.DATASOURCE_URL, "jdbc:h2:mem:dbr4-mt");
HapiProperties.setProperty(HapiProperties.FHIR_VERSION, "R4");
HapiProperties.setProperty(HapiProperties.SUBSCRIPTION_WEBSOCKET_ENABLED, "true");
HapiProperties.setProperty(HapiProperties.PARTITIONING_ENABLED, "true");
HapiProperties.setProperty(HapiProperties.PARTITIONING_MULTITENANCY_ENABLED, "true");
ourCtx = FhirContext.forR4();
}
@Test @Test
public void testCreateAndReadInTenantA() { public void testCreateAndReadInTenantA() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
// Create tenant A // Create tenant A
ourClientTenantInterceptor.setTenantId("DEFAULT"); ourClientTenantInterceptor.setTenantId("DEFAULT");
@@ -70,7 +68,7 @@ public class MultitenantServerR4IT {
@Test @Test
public void testCreateAndReadInTenantB() { public void testCreateAndReadInTenantB() {
ourLog.info("Base URL is: " + HapiProperties.getServerAddress());
// Create tenant A // Create tenant A
ourClientTenantInterceptor.setTenantId("DEFAULT"); ourClientTenantInterceptor.setTenantId("DEFAULT");
@@ -94,45 +92,16 @@ public class MultitenantServerR4IT {
assertEquals("Family B", pt2.getName().get(0).getFamily()); assertEquals("Family B", pt2.getName().get(0).getFamily());
} }
@AfterAll @BeforeEach
public static void afterClass() throws Exception { void beforeEach() {
ourServer.stop();
}
@BeforeAll
public static void beforeClass() throws Exception {
String path = Paths.get("").toAbsolutePath().toString();
ourLog.info("Project base path is: {}", path);
ourServer = new Server(0);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setContextPath("/hapi-fhir-jpaserver");
webAppContext.setDisplayName("HAPI FHIR");
webAppContext.setDescriptor(path + "/src/main/webapp/WEB-INF/web.xml");
webAppContext.setResourceBase(path + "/target/hapi-fhir-jpaserver-starter");
webAppContext.setParentLoaderPriority(true);
ourServer.setHandler(webAppContext);
ourServer.start();
ourPort = JettyUtil.getPortForStartedServer(ourServer);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = HapiProperties.getServerAddress();
ourServerBase = "http://localhost:" + ourPort + "/hapi-fhir-jpaserver/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
ourClientTenantInterceptor = new UrlTenantSelectionInterceptor(); ourClientTenantInterceptor = new UrlTenantSelectionInterceptor();
ourCtx = FhirContext.forR4();
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000);
String ourServerBase = "http://localhost:" + port + "/fhir/";
ourClient = ourCtx.newRestfulGenericClient(ourServerBase);
ourClient.registerInterceptor(new LoggingInterceptor(true));
ourClient.registerInterceptor(ourClientTenantInterceptor); ourClient.registerInterceptor(ourClientTenantInterceptor);
} }
public static void main(String[] theArgs) throws Exception {
ourPort = 8080;
beforeClass();
}
} }

View File

@@ -1,36 +0,0 @@
package ca.uhn.fhir.jpa.starter;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
/**
* Provides server ports
*/
public class RandomServerPortProvider {
private static List<Integer> ourPorts = new ArrayList<Integer>();
public static int findFreePort() {
ServerSocket server;
try {
server = new ServerSocket(0);
int port = server.getLocalPort();
ourPorts.add(port);
server.close();
Thread.sleep(500);
return port;
} catch (IOException e) {
throw new Error(e);
} catch (InterruptedException e) {
throw new Error(e);
}
}
public static List<Integer> list() {
return ourPorts;
}
}

View File

@@ -0,0 +1,104 @@
spring:
datasource:
url: 'jdbc:h2:file:./target/database/h2'
username: sa
password: null
driverClassName: org.h2.Driver
max-active: 15
profiles:
### This is the FHIR version. Choose between, dstu2, dstu3, r4 or r5
active: r4
hapi:
fhir:
#supported_resource_types:
# - Patient
# - Observation
# allow_cascading_deletes: true
# allow_contains_searches: true
# allow_external_references: true
# allow_multiple_delete: true
# allow_override_default_search_params: true
# allow_placeholder_references: true
# auto_create_placeholder_reference_targets: false
# default_encoding: JSON
# default_pretty_print: true
# default_page_size: 20
# enable_index_missing_fields: false
# enforce_referential_integrity_on_delete: false
# enforce_referential_integrity_on_write: false
# etag_support_enabled: true
# expunge_enabled: true
# daoconfig_client_id_strategy: null
# fhirpath_interceptor_enabled: false
# filter_search_enabled: true
# graphql_enabled: true
#partitioning:
# cross_partition_reference_mode: true
# multitenancy_enabled: true
# partitioning_include_in_search_hashes: true
#cors:
# allow_Credentials: true
# Supports multiple, comma separated allowed origin entries
# cors.allowed_origin=http://localhost:8080,https://localhost:8080,https://fhirtest.uhn.ca
# allowed_origin:
# - '*'
# logger:
# error_format: 'ERROR - ${requestVerb} ${requestUrl}'
# format: >-
# Path[${servletPath}] Source[${requestHeader.x-forwarded-for}]
# Operation[${operationType} ${operationName} ${idOrResourceName}]
# UA[${requestHeader.user-agent}] Params[${requestParameters}]
# ResponseEncoding[${responseEncodingNoDefault}]
# log_exceptions: true
# name: fhirtest.access
# max_binary_size: 104857600
# max_page_size: 200
# retain_cached_searches_mins: 60
# reuse_cached_search_results_millis: 60000
tester:
-
id: home
name: Local Tester
server_address: 'http://localhost:8080/hapi-fhir-jpaserver/fhir'
refuse_to_fetch_third_party_urls: false
fhir_version: R4
-
id: global
name: Global Tester
server_address: "http://hapi.fhir.org/baseR4"
refuse_to_fetch_third_party_urls: false
fhir_version: R4
# validation:
# requests_enabled: true
# responses_enabled: true
# binary_storage_enabled: true
# bulk_export_enabled: true
# partitioning_multitenancy_enabled:
# subscription:
# resthook_enabled: false
# websocket_enabled: false
# email:
# from: some@test.com
# host: google.com
# port:
# username:
# password:
# auth:
# startTlsEnable:
# startTlsRequired:
# quitWait:
#
#elasticsearch:
# debug:
# pretty_print_json_log: false
# refresh_after_write: false
# enabled: false
# password: SomePassword
# required_index_status: YELLOW
# rest_url: 'http://localhost:9200'
# schema_management_strategy: CREATE
# username: SomeUsername