├── .dockerignore ├── .editorconfig ├── .github ├── ct │ ├── chart-schema.yaml │ └── config.yaml └── workflows │ ├── build-images.yaml │ ├── chart-release.yaml │ ├── chart-test.yaml │ ├── maven.yml │ ├── smoke-tests.yml │ ├── spotless-check.yml │ └── stale.yaml ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── build-docker-image.bat ├── build-docker-image.sh ├── catalina.properties ├── charts └── hapi-fhir-jpaserver │ ├── .helmignore │ ├── Chart.lock │ ├── Chart.yaml │ ├── README.md │ ├── README.md.gotmpl │ ├── ci │ ├── custom-postgres-user-values.yaml │ ├── enabled-ingress-values.yaml │ ├── extra-config-values.yaml │ └── extra-volumes-values.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── application-config.yaml │ ├── deployment.yaml │ ├── externaldb-secret.yaml │ ├── ingress.yaml │ ├── poddisruptionbudget.yaml │ ├── service.yaml │ ├── serviceaccount.yaml │ ├── servicemonitor.yaml │ └── tests │ │ └── test-endpoints.yaml │ └── values.yaml ├── configs └── app │ └── index.html ├── custom ├── about.html ├── logo.jpg └── welcome.html ├── docker-build.bat ├── docker-compose.yml ├── pom.xml ├── server.xml └── src ├── main ├── java │ └── ca │ │ └── uhn │ │ └── fhir │ │ └── jpa │ │ └── starter │ │ ├── AppProperties.java │ │ ├── Application.java │ │ ├── annotations │ │ ├── OnCorsPresent.java │ │ ├── OnDSTU2Condition.java │ │ ├── OnDSTU3Condition.java │ │ ├── OnEitherVersion.java │ │ ├── OnImplementationGuidesPresent.java │ │ ├── OnR4BCondition.java │ │ ├── OnR4Condition.java │ │ └── OnR5Condition.java │ │ ├── cdshooks │ │ ├── CdsHooksConfigCondition.java │ │ ├── CdsHooksProperties.java │ │ ├── CdsHooksRequest.java │ │ ├── CdsHooksServlet.java │ │ ├── ErrorHandling.java │ │ ├── ModuleConfigurationPrefetchSvc.java │ │ ├── ProviderConfiguration.java │ │ ├── StarterCdsHooksConfig.java │ │ ├── UpdatedCdsCrService.java │ │ └── UpdatedCrDiscoveryService.java │ │ ├── common │ │ ├── ElasticsearchConfig.java │ │ ├── FhirServerConfigCommon.java │ │ ├── FhirServerConfigDstu2.java │ │ ├── FhirServerConfigDstu3.java │ │ ├── FhirServerConfigR4.java │ │ ├── FhirServerConfigR4B.java │ │ ├── FhirServerConfigR5.java │ │ ├── FhirTesterConfig.java │ │ ├── FhirTesterConfigCondition.java │ │ ├── PartitionModeConfigurer.java │ │ ├── StarterJpaConfig.java │ │ └── validation │ │ │ ├── IRepositoryValidationInterceptorFactory.java │ │ │ ├── OnRemoteTerminologyPresent.java │ │ │ ├── RepositoryValidationInterceptorFactoryDstu3.java │ │ │ ├── RepositoryValidationInterceptorFactoryR4.java │ │ │ ├── RepositoryValidationInterceptorFactoryR4B.java │ │ │ └── RepositoryValidationInterceptorFactoryR5.java │ │ ├── cr │ │ ├── CareGapsProperties.java │ │ ├── CqlCompilerProperties.java │ │ ├── CqlProperties.java │ │ ├── CqlRuntimeProperties.java │ │ ├── CrCommonConfig.java │ │ ├── CrConfigCondition.java │ │ ├── CrProperties.java │ │ ├── PostInitProviderRegisterer.java │ │ ├── StarterCrDstu3Config.java │ │ └── StarterCrR4Config.java │ │ ├── ig │ │ ├── IImplementationGuideOperationProvider.java │ │ ├── IgConfigCondition.java │ │ ├── ImplementationGuideR4OperationProvider.java │ │ └── ImplementationGuideR5OperationProvider.java │ │ ├── ips │ │ ├── IpsConfigCondition.java │ │ └── StarterIpsConfig.java │ │ ├── mdm │ │ ├── MdmConfig.java │ │ └── MdmConfigCondition.java │ │ ├── terminology │ │ └── TerminologyConfig.java │ │ ├── util │ │ ├── EnvironmentHelper.java │ │ └── JpaHibernatePropertiesProvider.java │ │ └── web │ │ ├── CustomContentFilesConfigurer.java │ │ ├── JobController.java │ │ └── WebAppFilesConfigurer.java ├── resources │ ├── application.yaml │ ├── cds.application.yaml │ ├── logback.xml │ └── mdm-rules.json └── webapp │ ├── WEB-INF │ ├── templates │ │ ├── about.html │ │ ├── tmpl-banner.html │ │ ├── tmpl-footer.html │ │ └── tmpl-home-welcome.html │ └── xsd │ │ ├── javaee_6.xsd │ │ ├── jsp_2_2.xsd │ │ ├── web-app_3_0.xsd │ │ ├── web-common_3_0.xsd │ │ └── xml.xsd │ ├── img │ ├── favicon.ico │ └── sample-logo.jpg │ └── js │ └── moment-with-locales.min.js └── test ├── java ├── ca │ └── uhn │ │ └── fhir │ │ └── jpa │ │ └── starter │ │ ├── CdsHooksServletIT.java │ │ ├── CustomBeanTest.java │ │ ├── CustomInterceptorTest.java │ │ ├── CustomOperationTest.java │ │ ├── ElasticsearchLastNR4IT.java │ │ ├── ExampleServerDbpmR5IT.java │ │ ├── ExampleServerDstu2IT.java │ │ ├── ExampleServerDstu3IT.java │ │ ├── ExampleServerR4BIT.java │ │ ├── ExampleServerR4IT.java │ │ ├── ExampleServerR5IT.java │ │ ├── IServerSupport.java │ │ ├── MdmTest.java │ │ ├── MeasureEvaluationConfig.java │ │ ├── MultitenantServerR4IT.java │ │ ├── ParallelUpdatesVersionConflictTest.java │ │ └── SocketImplementation.java └── some │ └── custom │ └── pkg1 │ ├── CustomBean.java │ ├── CustomInterceptorBean.java │ ├── CustomInterceptorPojo.java │ ├── CustomOperationBean.java │ └── CustomOperationPojo.java ├── resources ├── application.yaml ├── dstu3 │ └── EXM104 │ │ ├── EXM104_FHIR3-8.1.000-bundle.json │ │ ├── EXM104_FHIR3-8.1.000-files │ │ ├── library-deps-EXM104_FHIR3-8.1.000-bundle.json │ │ └── measure-EXM104_FHIR3-8.1.000.json │ │ └── EXTRA │ │ ├── library-EXM104_FHIR3-8.1.000.json │ │ ├── measurereport-denom-EXM104_FHIR3-8.1.000-expectedresults.json │ │ ├── measurereport-numer-EXM104_FHIR3-8.1.000-expectedresults.json │ │ ├── tests-denom-EXM104_FHIR3-bundle.json │ │ ├── tests-numer-EXM104_FHIR3-bundle.json │ │ └── valuesets-EXM104_FHIR3-8.1.000-bundle.json └── r4 │ ├── CareGaps │ ├── BreastCancerScreeningFHIR-bundle.json │ ├── authreporter-bundle.json │ └── numer-EXM125-patient.json │ ├── EXM104 │ └── EXM104-8.2.000-bundle.json │ ├── EXM130 │ └── EXM130-7.3.000-bundle.json │ ├── EXM349 │ └── EXM349-2.10.000-bundle.json.manuallyeditedtoremovesupplementaldata │ ├── HelloWorld-Bundle.json │ └── opioidcds-10-order-sign-bundle.json └── smoketest ├── SMOKE_TEST.md ├── http-client.env.json ├── plain_server.http └── smoketestfiles ├── patient_batch_create.json ├── patient_create.json ├── patient_patch.json ├── patient_process_message.json └── patient_update.json /.dockerignore: -------------------------------------------------------------------------------- 1 | target/classes/ 2 | target/failsafe-reports/ 3 | target/generated-* 4 | target/maven-* 5 | target/ROOT 6 | target/test-classes/ 7 | target/war 8 | target/duplicate-finder-result.xml 9 | target/jacoco.exec 10 | target/*.original 11 | .idea 12 | .git -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.java] 2 | charset = utf-8 3 | indent_style = tab 4 | indent_size = 3 5 | 6 | -------------------------------------------------------------------------------- /.github/ct/chart-schema.yaml: -------------------------------------------------------------------------------- 1 | name: str() 2 | home: str() 3 | version: str() 4 | apiVersion: str() 5 | appVersion: any(str(), num(), required=False) 6 | type: str() 7 | dependencies: any(required=False) 8 | description: str() 9 | keywords: list(str(), required=False) 10 | sources: list(str(), required=False) 11 | maintainers: list(include('maintainer'), required=False) 12 | icon: str(required=False) 13 | engine: str(required=False) 14 | condition: str(required=False) 15 | tags: str(required=False) 16 | deprecated: bool(required=False) 17 | kubeVersion: str(required=False) 18 | annotations: map(str(), str(), required=False) 19 | --- 20 | maintainer: 21 | name: str() 22 | email: str(required=False) 23 | url: str(required=False) 24 | -------------------------------------------------------------------------------- /.github/ct/config.yaml: -------------------------------------------------------------------------------- 1 | debug: true 2 | remote: origin 3 | chart-yaml-schema: .github/ct/chart-schema.yaml 4 | validate-maintainers: false 5 | validate-chart-schema: true 6 | validate-yaml: true 7 | check-version-increment: true 8 | chart-dirs: 9 | - charts 10 | helm-extra-args: --timeout 300s 11 | upgrade: true 12 | skip-missing-values: true 13 | release-label: release 14 | release-name-template: "helm-v{{ .Version }}" 15 | target-branch: master 16 | -------------------------------------------------------------------------------- /.github/workflows/build-images.yaml: -------------------------------------------------------------------------------- 1 | name: Build Container Images 2 | 3 | on: 4 | push: 5 | tags: 6 | - "image/v*" 7 | paths-ignore: 8 | - "charts/**" 9 | pull_request: 10 | branches: [master] 11 | paths-ignore: 12 | - "charts/**" 13 | env: 14 | IMAGES: docker.io/hapiproject/hapi 15 | PLATFORMS: linux/amd64,linux/arm64/v8 16 | 17 | jobs: 18 | build: 19 | name: Build 20 | runs-on: ubuntu-22.04 21 | steps: 22 | - name: Container meta for default (distroless) image 23 | id: docker_meta 24 | uses: docker/metadata-action@v5 25 | with: 26 | images: ${{ env.IMAGES }} 27 | tags: | 28 | type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}} 29 | 30 | 31 | - name: Container meta for tomcat image 32 | id: docker_tomcat_meta 33 | uses: docker/metadata-action@v5 34 | with: 35 | images: ${{ env.IMAGES }} 36 | tags: | 37 | type=match,pattern=image/(.*),group=1,enable=${{github.event_name != 'pull_request'}} 38 | flavor: | 39 | suffix=-tomcat,onlatest=true 40 | 41 | - name: Set up QEMU 42 | uses: docker/setup-qemu-action@v3 43 | 44 | - name: Set up Docker Buildx 45 | uses: docker/setup-buildx-action@v3 46 | 47 | - name: Login to DockerHub 48 | uses: docker/login-action@v3 49 | if: github.event_name != 'pull_request' 50 | with: 51 | username: ${{ secrets.DOCKERHUB_USERNAME }} 52 | password: ${{ secrets.DOCKERHUB_TOKEN }} 53 | 54 | - name: Cache Docker layers 55 | uses: actions/cache@v3 56 | with: 57 | path: /tmp/.buildx-cache 58 | key: ${{ runner.os }}-buildx-${{ github.sha }} 59 | restore-keys: | 60 | ${{ runner.os }}-buildx- 61 | 62 | - name: Build and push default (distroless) image 63 | id: docker_build 64 | uses: docker/build-push-action@v5 65 | with: 66 | cache-from: type=local,src=/tmp/.buildx-cache 67 | cache-to: type=local,dest=/tmp/.buildx-cache 68 | push: ${{ github.event_name != 'pull_request' }} 69 | tags: ${{ steps.docker_meta.outputs.tags }} 70 | labels: ${{ steps.docker_meta.outputs.labels }} 71 | platforms: ${{ env.PLATFORMS }} 72 | target: default 73 | 74 | - name: Build and push tomcat image 75 | id: docker_build_tomcat 76 | uses: docker/build-push-action@v5 77 | with: 78 | cache-from: type=local,src=/tmp/.buildx-cache 79 | cache-to: type=local,dest=/tmp/.buildx-cache 80 | push: ${{ github.event_name != 'pull_request' }} 81 | tags: ${{ steps.docker_tomcat_meta.outputs.tags }} 82 | labels: ${{ steps.docker_tomcat_meta.outputs.labels }} 83 | platforms: ${{ env.PLATFORMS }} 84 | target: tomcat 85 | -------------------------------------------------------------------------------- /.github/workflows/chart-release.yaml: -------------------------------------------------------------------------------- 1 | name: Release Charts 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - "charts/**" 9 | 10 | jobs: 11 | release: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - name: Add workspace as safe directory 15 | run: | 16 | git config --global --add safe.directory /__w/hapi-fhir-jpaserver-starter/hapi-fhir-jpaserver-starter 17 | 18 | - name: Checkout 19 | uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Configure Git 24 | run: | 25 | git config user.name "$GITHUB_ACTOR" 26 | git config user.email "$GITHUB_ACTOR@users.noreply.github.com" 27 | 28 | - name: Update dependencies 29 | run: find charts/ ! -path charts/ -maxdepth 1 -type d -exec helm dependency update {} \; 30 | 31 | - name: Run chart-releaser 32 | uses: helm/chart-releaser-action@be16258da8010256c6e82849661221415f031968 # v1.5.0 33 | with: 34 | config: .github/ct/config.yaml 35 | env: 36 | CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 37 | -------------------------------------------------------------------------------- /.github/workflows/chart-test.yaml: -------------------------------------------------------------------------------- 1 | name: Lint and Test Charts 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | paths: 8 | - "charts/**" 9 | 10 | jobs: 11 | lint: 12 | runs-on: ubuntu-22.04 13 | container: quay.io/helmpack/chart-testing:v3.11.0@sha256:f2fd21d30b64411105c7eafb1862783236a219d29f2292219a09fe94ca78ad2a 14 | steps: 15 | - name: Install helm-docs 16 | working-directory: /tmp 17 | env: 18 | HELM_DOCS_URL: https://github.com/norwoodj/helm-docs/releases/download/v1.14.2/helm-docs_1.14.2_Linux_x86_64.tar.gz 19 | run: | 20 | curl -LSs $HELM_DOCS_URL | tar xz && \ 21 | mv ./helm-docs /usr/local/bin/helm-docs && \ 22 | chmod +x /usr/local/bin/helm-docs && \ 23 | helm-docs --version 24 | 25 | - name: Add workspace as safe directory 26 | run: | 27 | git config --global --add safe.directory /__w/hapi-fhir-jpaserver-starter/hapi-fhir-jpaserver-starter 28 | 29 | - name: Checkout 30 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 31 | with: 32 | fetch-depth: 0 33 | 34 | - name: Check if documentation is up-to-date 35 | run: helm-docs && git diff --exit-code HEAD 36 | 37 | - name: Run chart-testing (lint) 38 | run: ct lint --config .github/ct/config.yaml 39 | 40 | test: 41 | runs-on: ubuntu-22.04 42 | strategy: 43 | matrix: 44 | k8s-version: [1.30.8, 1.31.4, 1.32.0] 45 | needs: 46 | - lint 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 50 | with: 51 | fetch-depth: 0 52 | 53 | - name: Set up chart-testing 54 | uses: helm/chart-testing-action@e6669bcd63d7cb57cb4380c33043eebe5d111992 # v2.6.1 55 | 56 | - name: Run chart-testing (list-changed) 57 | id: list-changed 58 | run: | 59 | changed=$(ct list-changed --config .github/ct/config.yaml) 60 | if [[ -n "$changed" ]]; then 61 | echo "::set-output name=changed::true" 62 | fi 63 | 64 | - name: Create k8s Kind Cluster 65 | uses: helm/kind-action@dda0770415bac9fc20092cacbc54aa298604d140 # v1.8.0 66 | if: ${{ steps.list-changed.outputs.changed == 'true' }} 67 | with: 68 | cluster_name: kind-cluster-k8s-${{ matrix.k8s-version }} 69 | node_image: kindest/node:v${{ matrix.k8s-version }} 70 | 71 | - name: Run chart-testing (install) 72 | run: ct install --config .github/ct/config.yaml 73 | if: ${{ steps.list-changed.outputs.changed == 'true' }} 74 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: 9 | - '**' 10 | paths-ignore: 11 | - "charts/**" 12 | pull_request: 13 | branches: [ master ] 14 | paths-ignore: 15 | - "charts/**" 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: 17 28 | distribution: zulu 29 | - name: Build with Maven 30 | run: mvn -B package --file pom.xml verify 31 | -------------------------------------------------------------------------------- /.github/workflows/smoke-tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build the Java project with Maven and peform IntelliJ smoke tests 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Smoke Tests 5 | 6 | on: 7 | push: 8 | branches: 9 | - '**' 10 | paths-ignore: 11 | - "charts/**" 12 | pull_request: 13 | branches: [ master ] 14 | paths-ignore: 15 | - "charts/**" 16 | 17 | jobs: 18 | build_and_smoke_test: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout project 24 | uses: actions/checkout@v4 25 | 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v3 28 | with: 29 | java-version: 17 30 | distribution: zulu 31 | 32 | - name: Build with Maven 33 | run: mvn -B package --file pom.xml -Dmaven.test.skip=true 34 | 35 | - name: Docker Pull HTTP client 36 | run: docker pull jetbrains/intellij-http-client 37 | 38 | - name: Start server with jetty 39 | run: | 40 | mkdir -p logs 41 | mvn -P jetty spring-boot:run | tee logs/server.log & 42 | sleep 80 43 | 44 | - name: Execute smoke tests 45 | run: docker run --rm -v $PWD:/workdir --add-host host.docker.internal:host-gateway jetbrains/intellij-http-client -D src/test/smoketest/plain_server.http --env-file src/test/smoketest/http-client.env.json --env default 46 | 47 | - name: Show last server logs 48 | if: always() 49 | run: | 50 | echo "===== Last 200 Lines of Server Log =====" 51 | tail -n 200 logs/server.log || true 52 | 53 | - name: Highlight server errors 54 | if: always() 55 | run: | 56 | echo "===== Highlighted Server Errors =====" 57 | if grep 'ERROR' logs/server.log > /dev/null; then 58 | grep 'ERROR' logs/server.log | while read -r line; do 59 | echo "::error::${line}" 60 | done 61 | else 62 | echo "No errors found in server logs." 63 | fi 64 | 65 | - name: Upload server logs 66 | if: always() 67 | uses: actions/upload-artifact@v4 68 | with: 69 | name: server-logs 70 | path: logs/server.log 71 | -------------------------------------------------------------------------------- /.github/workflows/spotless-check.yml: -------------------------------------------------------------------------------- 1 | name: mvn spotless:check (Formatting) 2 | 3 | 4 | on: 5 | pull_request: 6 | types: [opened, reopened, synchronize] 7 | 8 | jobs: 9 | format-check: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | pull-requests: write 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v3 17 | with: 18 | java-version: '17' 19 | distribution: 'temurin' 20 | - name: spotless:check 21 | run: mvn spotless:check 22 | - uses: mshick/add-pr-comment@v2 23 | if: always() 24 | with: 25 | proxy-url: https://slack-bots.azure.smilecdr.com/robogary/github 26 | message-success: | 27 | Formatting check succeeded! 28 | message-failure: | 29 | **This Pull Request has failed the formatting check** 30 | 31 | Please run `mvn spotless:apply` or `mvn clean install -DskipTests` to fix the formatting issues. 32 | 33 | You can automate this auto-formatting process to execute on the git pre-push hook, by installing [pre-commit](https://pre-commit.com/) and then calling `pre-commit install --hook-type pre-push`. This will cause formatting to run automatically whenever you push. 34 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/stale@v9 11 | with: 12 | stale-issue-message: 'This issue is stale because it has been open 730 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 13 | stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' 14 | close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' 15 | close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' 16 | days-before-issue-stale: 730 17 | days-before-pr-stale: 45 18 | days-before-issue-close: 5 19 | days-before-pr-close: 10 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | target/ 3 | *.iml 4 | 5 | # Compiled class file 6 | *.class 7 | 8 | # Log file 9 | *.log 10 | 11 | # BlueJ files 12 | *.ctxt 13 | 14 | # Mobile Tools for Java (J2ME) 15 | .mtj.tmp/ 16 | 17 | # Package Files # 18 | *.jar 19 | *.war 20 | *.nar 21 | *.ear 22 | *.zip 23 | *.tar.gz 24 | *.rar 25 | 26 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 27 | hs_err_pid* 28 | ### Windows template 29 | # Windows thumbnail cache files 30 | Thumbs.db 31 | Thumbs.db:encryptable 32 | ehthumbs.db 33 | ehthumbs_vista.db 34 | 35 | # Dump file 36 | *.stackdump 37 | 38 | # Folder config file 39 | [Dd]esktop.ini 40 | 41 | # Recycle Bin used on file shares 42 | $RECYCLE.BIN/ 43 | 44 | # Windows Installer files 45 | *.cab 46 | *.msi 47 | *.msix 48 | *.msm 49 | *.msp 50 | 51 | # Windows shortcuts 52 | *.lnk 53 | 54 | ### JetBrains template 55 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 56 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 57 | .idea/ 58 | 59 | # CMake 60 | cmake-build-*/ 61 | 62 | # File-based project format 63 | *.iws 64 | 65 | # IntelliJ 66 | out/ 67 | 68 | # JIRA plugin 69 | atlassian-ide-plugin.xml 70 | 71 | # Crashlytics plugin (for Android Studio and IntelliJ) 72 | com_crashlytics_export_strings.xml 73 | crashlytics.properties 74 | crashlytics-build.properties 75 | fabric.properties 76 | 77 | ### Eclipse template 78 | .metadata 79 | bin/ 80 | tmp/ 81 | *.tmp 82 | *.bak 83 | *.swp 84 | *~.nib 85 | local.properties 86 | .settings/ 87 | .loadpath 88 | .recommenders 89 | 90 | # External tool builders 91 | .externalToolBuilders/ 92 | 93 | # Locally stored "Eclipse launch configurations" 94 | *.launch 95 | 96 | # PyDev specific (Python IDE for Eclipse) 97 | *.pydevproject 98 | 99 | # CDT-specific (C/C++ Development Tooling) 100 | .cproject 101 | 102 | # CDT- autotools 103 | .autotools 104 | 105 | # Java annotation processor (APT) 106 | .factorypath 107 | 108 | # PDT-specific (PHP Development Tools) 109 | .buildpath 110 | 111 | # sbteclipse plugin 112 | .target 113 | 114 | # Tern plugin 115 | .tern-project 116 | 117 | # TeXlipse plugin 118 | .texlipse 119 | 120 | # STS (Spring Tool Suite) 121 | .springBeans 122 | .apt_generated 123 | .classpath 124 | .factorypath 125 | .project 126 | .settings 127 | .springBeans 128 | .sts4-cache 129 | 130 | # Code Recommenders 131 | .recommenders/ 132 | 133 | # Annotation Processing 134 | .apt_generated/ 135 | 136 | # Scala IDE specific (Scala & Java development for Eclipse) 137 | .cache-main 138 | .scala_dependencies 139 | .worksheet 140 | 141 | ### macOS template 142 | # General 143 | .DS_Store 144 | .AppleDouble 145 | .LSOverride 146 | 147 | # Icon must end with two \r 148 | Icon 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | .com.apple.timemachine.donotpresent 161 | 162 | # Directories potentially created on remote AFP share 163 | .AppleDB 164 | .AppleDesktop 165 | Network Trash Folder 166 | Temporary Items 167 | .apdisk 168 | 169 | # Helm Chart dependencies 170 | **/charts/*.tgz 171 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Use docker-based build environment (instead of openvz) 2 | #sudo: false 3 | #dist: trusty 4 | 5 | # Use VM based build environment 6 | sudo: required 7 | dist: trusty 8 | 9 | language: java 10 | jdk: 11 | - openjdk11 12 | #- oraclejdk9 13 | 14 | env: 15 | global: 16 | - MAVEN_OPTS="-Xmx10244M -Xss128M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=1024M -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC" 17 | 18 | cache: 19 | directories: 20 | - '$HOME/.m2/repository' 21 | 22 | install: /bin/true 23 | 24 | before_script: 25 | # This seems to be required to get travis to set Xmx4g, per https://github.com/travis-ci/travis-ci/issues/3893 26 | - export MAVEN_SKIP_RC=true 27 | # Sometimes things get restored from the cache with bad permissions. See https://github.com/travis-ci/travis-ci/issues/9630 28 | - sudo chmod -R 777 "$HOME/.m2/repository"; 29 | - sudo chown -R travis:travis "$HOME/.m2/repository"; 30 | 31 | script: 32 | - mvn -B install 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "java", 5 | "name": "Spring Boot-Application", 6 | "request": "launch", 7 | "cwd": "${workspaceFolder}", 8 | "mainClass": "ca.uhn.fhir.jpa.starter.Application", 9 | "projectName": "hapi-fhir-jpaserver-starter", 10 | "vmArgs": [ 11 | "-XX:TieredStopAtLevel=1", 12 | // "-Ddebug=true", 13 | // "-Dloader.debug=true", 14 | "-Dhapi.fhir.bulk_export_enabled=false", 15 | "-Dspring.batch.job.enabled=false", 16 | "-Dspring.main.allow-bean-definition-overriding=true", 17 | "-Dhapi.fhir.cdshooks.enabled=true", 18 | "-Dhapi.fhir.cr.enabled=true", 19 | "-Dspring.main.allow-bean-definition-overriding=true" 20 | 21 | ], 22 | "envFile": "${workspaceFolder}/.env" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/.classpath": true, 4 | "**/.project": true, 5 | "**/.settings": true, 6 | "**/.factorypath": true 7 | }, 8 | "java.compile.nullAnalysis.mode": "disabled", 9 | "java.configuration.updateBuildConfiguration": "automatic" 10 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/maven:3.9.9-eclipse-temurin-17 AS build-hapi 2 | WORKDIR /tmp/hapi-fhir-jpaserver-starter 3 | 4 | ARG OPENTELEMETRY_JAVA_AGENT_VERSION=1.33.3 5 | RUN curl -LSsO https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/v${OPENTELEMETRY_JAVA_AGENT_VERSION}/opentelemetry-javaagent.jar 6 | 7 | COPY pom.xml . 8 | COPY server.xml . 9 | RUN mvn -ntp dependency:go-offline 10 | 11 | COPY src/ /tmp/hapi-fhir-jpaserver-starter/src/ 12 | RUN mvn clean install -DskipTests -Djdk.lang.Process.launchMechanism=vfork 13 | 14 | FROM build-hapi AS build-distroless 15 | RUN mvn package -DskipTests spring-boot:repackage -Pboot 16 | RUN mkdir /app && cp /tmp/hapi-fhir-jpaserver-starter/target/ROOT.war /app/main.war 17 | 18 | 19 | ########### bitnami tomcat version is suitable for debugging and comes with a shell 20 | ########### it can be built using eg. `docker build --target tomcat .` 21 | FROM bitnami/tomcat:10.1 AS tomcat 22 | 23 | USER root 24 | RUN rm -rf /opt/bitnami/tomcat/webapps/ROOT && \ 25 | mkdir -p /opt/bitnami/hapi/data/hapi/lucenefiles && \ 26 | chown -R 1001:1001 /opt/bitnami/hapi/data/hapi/lucenefiles && \ 27 | chmod 775 /opt/bitnami/hapi/data/hapi/lucenefiles 28 | 29 | RUN mkdir -p /target && chown -R 1001:1001 target 30 | USER 1001 31 | 32 | COPY --chown=1001:1001 catalina.properties /opt/bitnami/tomcat/conf/catalina.properties 33 | COPY --chown=1001:1001 server.xml /opt/bitnami/tomcat/conf/server.xml 34 | COPY --from=build-hapi --chown=1001:1001 /tmp/hapi-fhir-jpaserver-starter/target/ROOT.war /opt/bitnami/tomcat/webapps/ROOT.war 35 | COPY --from=build-hapi --chown=1001:1001 /tmp/hapi-fhir-jpaserver-starter/opentelemetry-javaagent.jar /app 36 | 37 | ENV ALLOW_EMPTY_PASSWORD=yes 38 | 39 | ########### distroless brings focus on security and runs on plain spring boot - this is the default image 40 | FROM gcr.io/distroless/java17-debian12:nonroot AS default 41 | # 65532 is the nonroot user's uid 42 | # used here instead of the name to allow Kubernetes to easily detect that the container 43 | # is running as a non-root (uid != 0) user. 44 | USER 65532:65532 45 | WORKDIR /app 46 | 47 | COPY --chown=nonroot:nonroot --from=build-distroless /app /app 48 | COPY --chown=nonroot:nonroot --from=build-hapi /tmp/hapi-fhir-jpaserver-starter/opentelemetry-javaagent.jar /app 49 | 50 | ENTRYPOINT ["java", "--class-path", "/app/main.war", "-Dloader.path=main.war!/WEB-INF/classes/,main.war!/WEB-INF/,/app/extra-classes", "org.springframework.boot.loader.PropertiesLauncher"] 51 | -------------------------------------------------------------------------------- /build-docker-image.bat: -------------------------------------------------------------------------------- 1 | docker build -t hapi-fhir/hapi-fhir-jpaserver-starter . -------------------------------------------------------------------------------- /build-docker-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | docker build -t hapi-fhir/hapi-fhir-jpaserver-starter . 4 | 5 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *.orig 18 | *~ 19 | # Various IDEs 20 | .project 21 | .idea/ 22 | *.tmproj 23 | .vscode/ 24 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: postgresql 3 | repository: oci://registry-1.docker.io/bitnamicharts 4 | version: 16.5.5 5 | - name: common 6 | repository: oci://registry-1.docker.io/bitnamicharts 7 | version: 2.30.0 8 | digest: sha256:c114b296b53007a7973260de8bad61917fe741c0ad9de15fdbeea5743c8f4910 9 | generated: "2025-03-21T21:44:22.334197366+01:00" 10 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: hapi-fhir-jpaserver 3 | description: A Helm chart for deploying the HAPI FHIR JPA server starter on Kubernetes. 4 | type: application 5 | home: https://github.com/hapifhir/hapi-fhir-jpaserver-starter 6 | sources: 7 | - https://github.com/hapifhir/hapi-fhir-jpaserver-starter 8 | dependencies: 9 | - name: postgresql 10 | version: 16.5.5 11 | repository: oci://registry-1.docker.io/bitnamicharts 12 | condition: postgresql.enabled 13 | - name: common 14 | repository: oci://registry-1.docker.io/bitnamicharts 15 | version: 2.30.0 16 | appVersion: 8.0.0 17 | version: 0.19.0 18 | annotations: 19 | artifacthub.io/license: Apache-2.0 20 | artifacthub.io/containsSecurityUpdates: "false" 21 | artifacthub.io/operator: "false" 22 | artifacthub.io/prerelease: "false" 23 | artifacthub.io/recommendations: | 24 | - url: https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack 25 | - url: https://artifacthub.io/packages/helm/bitnami/postgresql 26 | artifacthub.io/changes: | 27 | # When using the list of objects option the valid supported kinds are 28 | # added, changed, deprecated, removed, fixed, and security. 29 | - kind: changed 30 | description: "updated postgresql sub-chart to 16.5.5" 31 | - kind: changed 32 | description: "updated curlimages/curl to 8.12.1" 33 | - kind: changed 34 | description: "updated hapiproject/hapi to v8.0.0-1" 35 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/README.md.gotmpl: -------------------------------------------------------------------------------- 1 | # HAPI FHIR JPA Server Starter Helm Chart 2 | 3 | {{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} 4 | 5 | This helm chart will help you install the HAPI FHIR JPA Server in a Kubernetes environment. 6 | 7 | ## Sample usage 8 | 9 | ```sh 10 | helm repo add hapifhir https://hapifhir.github.io/hapi-fhir-jpaserver-starter/ 11 | helm install hapi-fhir-jpaserver hapifhir/hapi-fhir-jpaserver 12 | ``` 13 | 14 | {{ template "chart.requirementsSection" . }} 15 | 16 | {{ template "chart.valuesSection" . }} 17 | 18 | ## Development 19 | 20 | To update the Helm chart when a new version of the `hapiproject/hapi` image is released, [values.yaml](values.yaml) `image.tag` and the [Chart.yaml](Chart.yaml)'s 21 | `version` and optionally the `appVersion` field need to be updated. Afterwards, re-generate the [README.md](README.md) 22 | by running: 23 | 24 | ```sh 25 | $ helm-docs 26 | INFO[2021-11-20T12:38:04Z] Found Chart directories [charts/hapi-fhir-jpaserver] 27 | INFO[2021-11-20T12:38:04Z] Generating README Documentation for chart /usr/src/app/charts/hapi-fhir-jpaserver 28 | ``` 29 | 30 | ## Enable Distributed Tracing based on the OpenTelemtry Java Agent 31 | 32 | The container image includes the [OpenTelemetry Java agent JAR](https://github.com/open-telemetry/opentelemetry-java-instrumentation) 33 | which can be used to enable distributed tracing. It can be configured entirely using environment variables, 34 | see for details. 35 | 36 | Here's an example setup deploying [Jaeger](https://www.jaegertracing.io/) as a tracing backend: 37 | 38 | ```sh 39 | # required by the Jaeger Operator 40 | kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.yaml 41 | kubectl create namespace observability 42 | kubectl create -f https://github.com/jaegertracing/jaeger-operator/releases/download/v1.37.0/jaeger-operator.yaml -n observability 43 | 44 | cat < in your browser. 78 | 79 | {{ template "helm-docs.versionFooter" . }} 80 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/ci/custom-postgres-user-values.yaml: -------------------------------------------------------------------------------- 1 | postgresql: 2 | enabled: true 3 | auth: 4 | username: hapi_fhir_jpaserver_starter_user 5 | database: hapi_fhir_jpaserver_starter 6 | password: secret_user_password 7 | postgresPassword: secret_postgres_password 8 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/ci/enabled-ingress-values.yaml: -------------------------------------------------------------------------------- 1 | ingress: 2 | enabled: true 3 | 4 | postgresql: 5 | auth: 6 | postgresPassword: secretpassword 7 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/ci/extra-config-values.yaml: -------------------------------------------------------------------------------- 1 | extraConfig: | 2 | hapi: 3 | fhir: 4 | cr_enabled: true 5 | tester: 6 | home: 7 | name: Hello HAPI FHIR 8 | server_address: "http://fhir-server.127.0.0.1.nip.io/fhir" 9 | refuse_to_fetch_third_party_urls: true 10 | fhir_version: R4 11 | 12 | ingress: 13 | enabled: true 14 | hosts: 15 | - host: fhir-server.127.0.0.1.nip.io 16 | pathType: ImplementationSpecific 17 | paths: ["/"] 18 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/ci/extra-volumes-values.yaml: -------------------------------------------------------------------------------- 1 | extraVolumes: 2 | - name: config-kube-root-ca 3 | configMap: 4 | name: kube-root-ca.crt 5 | items: 6 | - key: ca.crt 7 | path: ca.crt 8 | 9 | extraVolumeMounts: 10 | - name: config-kube-root-ca 11 | mountPath: /etc/test 12 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the application URL by running these commands: 2 | {{- if .Values.ingress.enabled }} 3 | {{- range $host := .Values.ingress.hosts }} 4 | {{- range .paths }} 5 | http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} 6 | {{- end }} 7 | {{- end }} 8 | {{- else if contains "NodePort" .Values.service.type }} 9 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "hapi-fhir-jpaserver.fullname" . }}) 10 | export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") 11 | echo http://$NODE_IP:$NODE_PORT 12 | {{- else if contains "LoadBalancer" .Values.service.type }} 13 | NOTE: It may take a few minutes for the LoadBalancer IP to be available. 14 | You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "hapi-fhir-jpaserver.fullname" . }}' 15 | export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "hapi-fhir-jpaserver.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") 16 | echo http://$SERVICE_IP:{{ .Values.service.port }} 17 | {{- else if contains "ClusterIP" .Values.service.type }} 18 | export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "hapi-fhir-jpaserver.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") 19 | export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") 20 | echo "Visit http://127.0.0.1:8080 to use your application" 21 | kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT 22 | {{- end }} 23 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "hapi-fhir-jpaserver.name" -}} 5 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "hapi-fhir-jpaserver.fullname" -}} 14 | {{- if .Values.fullnameOverride }} 15 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "hapi-fhir-jpaserver.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "hapi-fhir-jpaserver.labels" -}} 37 | helm.sh/chart: {{ include "hapi-fhir-jpaserver.chart" . }} 38 | {{ include "hapi-fhir-jpaserver.selectorLabels" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Selector labels 47 | */}} 48 | {{- define "hapi-fhir-jpaserver.selectorLabels" -}} 49 | app.kubernetes.io/name: {{ include "hapi-fhir-jpaserver.name" . }} 50 | app.kubernetes.io/instance: {{ .Release.Name }} 51 | {{- end }} 52 | 53 | {{/* 54 | Create the name of the service account to use 55 | */}} 56 | {{- define "hapi-fhir-jpaserver.serviceAccountName" -}} 57 | {{- if .Values.serviceAccount.create }} 58 | {{- default (include "hapi-fhir-jpaserver.fullname" .) .Values.serviceAccount.name }} 59 | {{- else }} 60 | {{- default "default" .Values.serviceAccount.name }} 61 | {{- end }} 62 | {{- end }} 63 | 64 | {{/* 65 | Create a default fully qualified postgresql name. 66 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 67 | */}} 68 | {{- define "hapi-fhir-jpaserver.postgresql.fullname" -}} 69 | {{- $name := default "postgresql" .Values.postgresql.nameOverride -}} 70 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 71 | {{- end -}} 72 | 73 | {{/* 74 | Get the Postgresql credentials secret name. 75 | */}} 76 | {{- define "hapi-fhir-jpaserver.postgresql.secretName" -}} 77 | {{- if .Values.postgresql.enabled -}} 78 | {{- if .Values.postgresql.auth.existingSecret -}} 79 | {{- printf "%s" .Values.postgresql.auth.existingSecret -}} 80 | {{- else -}} 81 | {{- printf "%s" (include "hapi-fhir-jpaserver.postgresql.fullname" .) -}} 82 | {{- end -}} 83 | {{- else }} 84 | {{- if .Values.externalDatabase.existingSecret -}} 85 | {{- printf "%s" .Values.externalDatabase.existingSecret -}} 86 | {{- else -}} 87 | {{ printf "%s-%s" (include "hapi-fhir-jpaserver.fullname" .) "external-db" }} 88 | {{- end -}} 89 | {{- end -}} 90 | {{- end -}} 91 | 92 | {{/* 93 | Get the Postgresql credentials secret key. 94 | */}} 95 | {{- define "hapi-fhir-jpaserver.postgresql.secretKey" -}} 96 | {{- if .Values.postgresql.enabled -}} 97 | {{- if .Values.postgresql.auth.username -}} 98 | {{- printf "%s" .Values.postgresql.auth.secretKeys.userPasswordKey -}} 99 | {{- else -}} 100 | {{- printf "%s" .Values.postgresql.auth.secretKeys.adminPasswordKey -}} 101 | {{- end -}} 102 | {{- else }} 103 | {{- if .Values.externalDatabase.existingSecret -}} 104 | {{- printf "%s" .Values.externalDatabase.existingSecretKey -}} 105 | {{- else -}} 106 | {{- printf "postgres-password" -}} 107 | {{- end -}} 108 | {{- end -}} 109 | {{- end -}} 110 | 111 | {{/* 112 | Add environment variables to configure database values 113 | */}} 114 | {{- define "hapi-fhir-jpaserver.database.host" -}} 115 | {{- ternary (include "hapi-fhir-jpaserver.postgresql.fullname" .) .Values.externalDatabase.host .Values.postgresql.enabled -}} 116 | {{- end -}} 117 | 118 | {{/* 119 | Add environment variables to configure database values 120 | */}} 121 | {{- define "hapi-fhir-jpaserver.database.user" -}} 122 | {{- if .Values.postgresql.enabled -}} 123 | {{- printf "%s" .Values.postgresql.auth.username | default "postgres" -}} 124 | {{- else -}} 125 | {{- printf "%s" .Values.externalDatabase.user -}} 126 | {{- end -}} 127 | {{- end -}} 128 | 129 | {{/* 130 | Add environment variables to configure database values 131 | */}} 132 | {{- define "hapi-fhir-jpaserver.database.name" -}} 133 | {{- ternary .Values.postgresql.auth.database .Values.externalDatabase.database .Values.postgresql.enabled -}} 134 | {{- end -}} 135 | 136 | {{/* 137 | Add environment variables to configure database values 138 | */}} 139 | {{- define "hapi-fhir-jpaserver.database.port" -}} 140 | {{- ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled -}} 141 | {{- end -}} 142 | 143 | {{/* 144 | Create the JDBC URL from the host, port and database name. 145 | */}} 146 | {{- define "hapi-fhir-jpaserver.database.jdbcUrl" -}} 147 | {{- $host := (include "hapi-fhir-jpaserver.database.host" .) -}} 148 | {{- $port := (include "hapi-fhir-jpaserver.database.port" .) -}} 149 | {{- $name := (include "hapi-fhir-jpaserver.database.name" .) -}} 150 | {{- $appName := .Release.Name -}} 151 | {{ printf "jdbc:postgresql://%s:%d/%s?ApplicationName=%s" $host (int $port) $name $appName }} 152 | {{- end -}} 153 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/application-config.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.extraConfig -}} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "hapi-fhir-jpaserver.fullname" . }}-application-config 6 | labels: 7 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 8 | data: 9 | application-extra.yaml: |- 10 | {{ .Values.extraConfig | nindent 4 }} 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "hapi-fhir-jpaserver.fullname" . }} 5 | labels: 6 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 7 | {{- with .Values.deploymentAnnotations }} 8 | annotations: 9 | {{- toYaml . | nindent 8 }} 10 | {{- end }} 11 | spec: 12 | replicas: {{ .Values.replicaCount }} 13 | selector: 14 | matchLabels: 15 | {{- include "hapi-fhir-jpaserver.selectorLabels" . | nindent 6 }} 16 | template: 17 | metadata: 18 | {{- with .Values.podAnnotations }} 19 | annotations: 20 | {{- toYaml . | nindent 8 }} 21 | {{- end }} 22 | labels: 23 | {{- include "hapi-fhir-jpaserver.selectorLabels" . | nindent 8 }} 24 | spec: 25 | {{- with .Values.imagePullSecrets }} 26 | imagePullSecrets: 27 | {{- toYaml . | nindent 8 }} 28 | {{- end }} 29 | serviceAccountName: {{ include "hapi-fhir-jpaserver.serviceAccountName" . }} 30 | securityContext: 31 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 32 | initContainers: 33 | - name: wait-for-db-to-be-ready 34 | image: docker.io/bitnami/postgresql:17.4.0-debian-12-r10@sha256:7b9af9dd759055265998bbf12368e6d7d6326e6fd23f8157be841fad0915c1a1 35 | imagePullPolicy: IfNotPresent 36 | {{- with .Values.restrictedContainerSecurityContext }} 37 | securityContext: 38 | {{- toYaml . | nindent 12 }} 39 | {{- end }} 40 | {{- if .Values.initContainers.resources }} 41 | resources: {{- toYaml .Values.initContainers.resources | nindent 12 }} 42 | {{- else if ne .Values.initContainers.resourcesPreset "none" }} 43 | resources: {{- include "common.resources.preset" (dict "type" .Values.initContainers.resourcesPreset) | nindent 12 }} 44 | {{- end }} 45 | env: 46 | - name: PGHOST 47 | value: "{{ include "hapi-fhir-jpaserver.database.host" . }}" 48 | - name: PGPORT 49 | value: "{{ include "hapi-fhir-jpaserver.database.port" . }}" 50 | - name: PGUSER 51 | value: "{{ include "hapi-fhir-jpaserver.database.user" . }}" 52 | command: ["/bin/sh", "-c"] 53 | args: 54 | - | 55 | until pg_isready; do 56 | echo "Waiting for DB ${PGUSER}@${PGHOST}:${PGPORT} to be up"; 57 | sleep 15; 58 | done; 59 | containers: 60 | - name: {{ .Chart.Name }} 61 | securityContext: 62 | {{- toYaml .Values.securityContext | nindent 12 }} 63 | image: {{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }} 64 | imagePullPolicy: {{ .Values.image.pullPolicy }} 65 | ports: 66 | - name: http 67 | containerPort: 8080 68 | protocol: TCP 69 | - name: http-metrics 70 | containerPort: 8081 71 | protocol: TCP 72 | {{- with .Values.startupProbe }} 73 | startupProbe: 74 | {{- toYaml . | nindent 12 }} 75 | {{- end }} 76 | {{- with .Values.livenessProbe }} 77 | livenessProbe: 78 | {{- toYaml . | nindent 12 }} 79 | {{- end }} 80 | {{- with .Values.readinessProbe }} 81 | readinessProbe: 82 | {{- toYaml . | nindent 12 }} 83 | {{- end }} 84 | {{- if .Values.resources }} 85 | resources: {{- toYaml .Values.resources | nindent 12 }} 86 | {{- else if ne .Values.resourcesPreset "none" }} 87 | resources: {{- include "common.resources.preset" (dict "type" .Values.resourcesPreset) | nindent 12 }} 88 | {{- end }} 89 | env: 90 | - name: SPRING_DATASOURCE_URL 91 | value: {{ include "hapi-fhir-jpaserver.database.jdbcUrl" $ }} 92 | - name: SPRING_DATASOURCE_USERNAME 93 | value: {{ include "hapi-fhir-jpaserver.database.user" $ }} 94 | - name: SPRING_DATASOURCE_PASSWORD 95 | valueFrom: 96 | secretKeyRef: 97 | name: {{ include "hapi-fhir-jpaserver.postgresql.secretName" . }} 98 | key: {{ include "hapi-fhir-jpaserver.postgresql.secretKey" . }} 99 | - name: SPRING_DATASOURCE_DRIVERCLASSNAME 100 | value: org.postgresql.Driver 101 | - name: spring.jpa.properties.hibernate.dialect 102 | value: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect 103 | - name: HAPI_FHIR_USE_APACHE_ADDRESS_STRATEGY 104 | value: "true" 105 | - name: MANAGEMENT_ENDPOINT_HEALTH_PROBES_ADD_ADDITIONAL_PATHS 106 | value: "true" 107 | - name: MANAGEMENT_SERVER_PORT 108 | value: "8081" 109 | - name: MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE 110 | value: "health,prometheus" 111 | {{- if .Values.extraConfig }} 112 | - name: SPRING_CONFIG_IMPORT 113 | value: "/app/config/application-extra.yaml" 114 | {{- end }} 115 | {{- if .Values.extraEnv }} 116 | {{ toYaml .Values.extraEnv | nindent 12 }} 117 | {{- end }} 118 | volumeMounts: 119 | - mountPath: /tmp 120 | name: tmp-volume 121 | - mountPath: /app/target 122 | name: lucenefiles-volume 123 | {{- if .Values.extraConfig }} 124 | - name: application-extra-config 125 | mountPath: /app/config/application-extra.yaml 126 | readOnly: true 127 | subPath: application-extra.yaml 128 | {{- end }} 129 | {{- if .Values.extraVolumeMounts }} 130 | {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }} 131 | {{- end }} 132 | {{- with .Values.nodeSelector }} 133 | nodeSelector: 134 | {{- toYaml . | nindent 8 }} 135 | {{- end }} 136 | {{- with .Values.affinity }} 137 | affinity: 138 | {{- toYaml . | nindent 8 }} 139 | {{- end }} 140 | {{- with .Values.tolerations }} 141 | tolerations: 142 | {{- toYaml . | nindent 8 }} 143 | {{- end }} 144 | {{- with .Values.topologySpreadConstraints }} 145 | topologySpreadConstraints: 146 | {{- toYaml . | nindent 8 }} 147 | {{- end }} 148 | volumes: 149 | - name: tmp-volume 150 | emptyDir: {} 151 | - name: lucenefiles-volume 152 | emptyDir: {} 153 | {{- if .Values.extraConfig }} 154 | - name: application-extra-config 155 | configMap: 156 | name: {{ include "hapi-fhir-jpaserver.fullname" . }}-application-config 157 | {{- end }} 158 | {{- if .Values.extraVolumes }} 159 | {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }} 160 | {{- end }} 161 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/externaldb-secret.yaml: -------------------------------------------------------------------------------- 1 | {{- if and (not .Values.postgresql.enabled) (not .Values.externalDatabase.existingSecret) (not .Values.postgresql.auth.existingSecret) }} 2 | apiVersion: v1 3 | kind: Secret 4 | metadata: 5 | name: {{ include "hapi-fhir-jpaserver.fullname" . }}-external-db 6 | labels: 7 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 8 | type: Opaque 9 | data: 10 | postgres-password: {{ .Values.externalDatabase.password | b64enc | quote }} 11 | {{- end }} 12 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled -}} 2 | {{- $fullName := include "hapi-fhir-jpaserver.fullname" . -}} 3 | {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} 4 | apiVersion: networking.k8s.io/v1 5 | {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion }} 6 | apiVersion: networking.k8s.io/v1beta1 7 | {{ else }} 8 | apiVersion: extensions/v1beta1 9 | {{- end }} 10 | kind: Ingress 11 | metadata: 12 | name: {{ $fullName }} 13 | labels: 14 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 15 | {{- with .Values.ingress.annotations }} 16 | annotations: 17 | {{- toYaml . | nindent 4 }} 18 | {{- end }} 19 | spec: 20 | {{- if .Values.ingress.tls }} 21 | tls: 22 | {{- range .Values.ingress.tls }} 23 | - hosts: 24 | {{- range .hosts }} 25 | - {{ . | quote }} 26 | {{- end }} 27 | secretName: {{ .secretName }} 28 | {{- end }} 29 | {{- end }} 30 | rules: 31 | {{- range .Values.ingress.hosts }} 32 | {{- $pathType := .pathType }} 33 | - host: {{ .host | quote }} 34 | http: 35 | paths: 36 | {{- range .paths }} 37 | - path: {{ . }} 38 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 39 | pathType: {{ $pathType | default "ImplementationSpecific" }} 40 | {{- end }} 41 | backend: 42 | {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} 43 | service: 44 | name: {{ $fullName }} 45 | port: 46 | name: http 47 | {{ else }} 48 | serviceName: {{ $fullName }} 49 | servicePort: http 50 | {{- end }} 51 | {{- end }} 52 | {{- end }} 53 | {{- end }} 54 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/poddisruptionbudget.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.podDisruptionBudget.enabled }} 2 | kind: PodDisruptionBudget 3 | apiVersion: policy/v1 4 | metadata: 5 | name: {{ include "hapi-fhir-jpaserver.fullname" . }} 6 | labels: 7 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 8 | spec: 9 | {{- if .Values.podDisruptionBudget.minAvailable }} 10 | minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} 11 | {{- end }} 12 | {{- if .Values.podDisruptionBudget.maxUnavailable }} 13 | maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} 14 | {{- end }} 15 | selector: 16 | matchLabels: 17 | {{- include "hapi-fhir-jpaserver.selectorLabels" . | nindent 6 }} 18 | {{- end }} 19 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "hapi-fhir-jpaserver.fullname" . }} 5 | labels: 6 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | - port: {{ .Values.metrics.service.port }} 15 | targetPort: http-metrics 16 | protocol: TCP 17 | name: http-metrics 18 | selector: 19 | {{- include "hapi-fhir-jpaserver.selectorLabels" . | nindent 4 }} 20 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "hapi-fhir-jpaserver.serviceAccountName" . }} 6 | labels: 7 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 8 | {{- with .Values.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | automountServiceAccountToken: {{ .Values.serviceAccount.automount }} 13 | {{- end }} 14 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.metrics.serviceMonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "hapi-fhir-jpaserver.fullname" . }} 6 | {{- if .Values.metrics.serviceMonitor.namespace }} 7 | namespace: {{ .Values.metrics.serviceMonitor.namespace }} 8 | {{- end }} 9 | labels: 10 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 11 | {{- if .Values.metrics.serviceMonitor.additionalLabels }} 12 | {{- toYaml .Values.metrics.serviceMonitor.additionalLabels | nindent 4 }} 13 | {{- end }} 14 | spec: 15 | endpoints: 16 | - port: http-metrics 17 | path: /actuator/prometheus 18 | {{- if .Values.metrics.serviceMonitor.interval }} 19 | interval: {{ .Values.metrics.serviceMonitor.interval }} 20 | {{- end }} 21 | {{- if .Values.metrics.serviceMonitor.scrapeTimeout }} 22 | scrapeTimeout: {{ .Values.metrics.serviceMonitor.scrapeTimeout }} 23 | {{- end }} 24 | namespaceSelector: 25 | matchNames: 26 | - {{ .Release.Namespace }} 27 | selector: 28 | matchLabels: 29 | {{- include "hapi-fhir-jpaserver.selectorLabels" . | nindent 6 }} 30 | {{- end }} 31 | -------------------------------------------------------------------------------- /charts/hapi-fhir-jpaserver/templates/tests/test-endpoints.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ include "hapi-fhir-jpaserver.fullname" . }}-test-endpoints" 5 | labels: 6 | {{- include "hapi-fhir-jpaserver.labels" . | nindent 4 }} 7 | {{ include "hapi-fhir-jpaserver.fullname" . }}-client: "true" 8 | app.kubernetes.io/component: tests 9 | annotations: 10 | "helm.sh/hook": test 11 | spec: 12 | restartPolicy: Never 13 | automountServiceAccountToken: {{ .Values.tests.automountServiceAccountToken }} 14 | securityContext: 15 | {{- toYaml .Values.tests.podSecurityContext | nindent 4 }} 16 | containers: 17 | - name: test-metadata-endpoint 18 | image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" 19 | command: ["curl", "--fail-with-body"] 20 | args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.service.port }}/fhir/metadata?_summary=true"] 21 | {{- with .Values.restrictedContainerSecurityContext }} 22 | securityContext: 23 | {{- toYaml . | nindent 8 }} 24 | {{- end }} 25 | {{- if .Values.tests.resources }} 26 | resources: {{- toYaml .Values.tests.resources | nindent 10 }} 27 | {{- else if ne .Values.tests.resourcesPreset "none" }} 28 | resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} 29 | {{- end }} 30 | livenessProbe: 31 | exec: 32 | command: ["true"] 33 | readinessProbe: 34 | exec: 35 | command: ["true"] 36 | - name: test-patient-endpoint 37 | image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" 38 | command: ["curl", "--fail-with-body"] 39 | args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.service.port }}/fhir/Patient?_count=1&_summary=true"] 40 | {{- with .Values.restrictedContainerSecurityContext }} 41 | securityContext: 42 | {{- toYaml . | nindent 8 }} 43 | {{- end }} 44 | {{- if .Values.tests.resources }} 45 | resources: {{- toYaml .Values.tests.resources | nindent 10 }} 46 | {{- else if ne .Values.tests.resourcesPreset "none" }} 47 | resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} 48 | {{- end }} 49 | livenessProbe: 50 | exec: 51 | command: ["true"] 52 | readinessProbe: 53 | exec: 54 | command: ["true"] 55 | - name: test-metrics-endpoint 56 | image: "{{ .Values.curl.image.registry }}/{{ .Values.curl.image.repository }}:{{ .Values.curl.image.tag }}" 57 | command: ["curl", "--fail-with-body"] 58 | args: ["http://{{ include "hapi-fhir-jpaserver.fullname" . }}:{{ .Values.metrics.service.port }}/actuator/prometheus"] 59 | {{- with .Values.restrictedContainerSecurityContext }} 60 | securityContext: 61 | {{- toYaml . | nindent 8 }} 62 | {{- end }} 63 | {{- if .Values.tests.resources }} 64 | resources: {{- toYaml .Values.tests.resources | nindent 10 }} 65 | {{- else if ne .Values.tests.resourcesPreset "none" }} 66 | resources: {{- include "common.resources.preset" (dict "type" .Values.tests.resourcesPreset) | nindent 10 }} 67 | {{- end }} 68 | livenessProbe: 69 | exec: 70 | command: ["true"] 71 | readinessProbe: 72 | exec: 73 | command: ["true"] 74 | -------------------------------------------------------------------------------- /configs/app/index.html: -------------------------------------------------------------------------------- 1 |

2 | Greetings from the custom web app page! 3 |

-------------------------------------------------------------------------------- /custom/about.html: -------------------------------------------------------------------------------- 1 |

2 | This is a custom about page! It means you have configured 'custom_content_path: ./custom' in the application.yaml 3 |

4 |

5 | This server provides a complete implementation of the FHIR Specification 6 | using a 100% open source software stack. 7 |

8 |

9 | This server is built 10 | from a number of modules of the 11 | HAPI FHIR 12 | project, which is a 100% open-source (Apache 2.0 Licensed) Java based 13 | implementation of the FHIR specification. 14 |

-------------------------------------------------------------------------------- /custom/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hapifhir/hapi-fhir-jpaserver-starter/7cd0637c88b0f8d8d8679d0dbf31bfa27e6c7bf2/custom/logo.jpg -------------------------------------------------------------------------------- /custom/welcome.html: -------------------------------------------------------------------------------- 1 |

2 | This is a custom welcome page! It means you have configured 'custom_content_path: ./custom' in the application.yaml 3 |

4 |

5 | This server provides a complete implementation of the FHIR Specification 6 | using a 100% open source software stack. 7 |

8 |

9 | This server is built 10 | from a number of modules of the 11 | HAPI FHIR 12 | project, which is a 100% open-source (Apache 2.0 Licensed) Java based 13 | implementation of the FHIR specification. 14 |

-------------------------------------------------------------------------------- /docker-build.bat: -------------------------------------------------------------------------------- 1 | docker build --tag hapiproject/hapi:latest --tag hapiproject/hapi:4.1 -m 4g . -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | hapi-fhir-jpaserver-start: 4 | build: . 5 | container_name: hapi-fhir-jpaserver-start 6 | restart: on-failure 7 | environment: 8 | SPRING_DATASOURCE_URL: "jdbc:postgresql://hapi-fhir-postgres:5432/hapi" 9 | SPRING_DATASOURCE_USERNAME: "admin" 10 | SPRING_DATASOURCE_PASSWORD: "admin" 11 | SPRING_DATASOURCE_DRIVERCLASSNAME: "org.postgresql.Driver" 12 | ports: 13 | - "8080:8080" 14 | hapi-fhir-postgres: 15 | image: postgres:15-alpine 16 | container_name: hapi-fhir-postgres 17 | restart: always 18 | environment: 19 | POSTGRES_DB: "hapi" 20 | POSTGRES_USER: "admin" 21 | POSTGRES_PASSWORD: "admin" 22 | volumes: 23 | - hapi-fhir-postgres:/var/lib/postgresql/data 24 | volumes: 25 | hapi-fhir-postgres: 26 | -------------------------------------------------------------------------------- /server.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 22 | 27 | 28 | 29 | 34 | 35 | 42 | 47 | 48 | 51 | 52 | 54 | 55 | 59 | 61 | 62 | 63 | 65 | 66 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/Application.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.batch2.jobs.config.Batch2JobsConfig; 4 | import ca.uhn.fhir.jpa.batch2.JpaBatch2Config; 5 | import ca.uhn.fhir.jpa.starter.annotations.OnEitherVersion; 6 | import ca.uhn.fhir.jpa.starter.cdshooks.StarterCdsHooksConfig; 7 | import ca.uhn.fhir.jpa.starter.cr.StarterCrDstu3Config; 8 | import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config; 9 | import ca.uhn.fhir.jpa.starter.mdm.MdmConfig; 10 | import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig; 11 | import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig; 12 | import ca.uhn.fhir.jpa.subscription.match.config.WebsocketDispatcherConfig; 13 | import ca.uhn.fhir.jpa.subscription.submit.config.SubscriptionSubmitterConfig; 14 | import ca.uhn.fhir.rest.server.RestfulServer; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 17 | import org.springframework.boot.SpringApplication; 18 | import org.springframework.boot.autoconfigure.SpringBootApplication; 19 | import org.springframework.boot.autoconfigure.elasticsearch.ElasticsearchRestClientAutoConfiguration; 20 | import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration; 21 | import org.springframework.boot.web.servlet.ServletComponentScan; 22 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 23 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 24 | import org.springframework.context.annotation.Bean; 25 | import org.springframework.context.annotation.Conditional; 26 | import org.springframework.context.annotation.Import; 27 | 28 | @ServletComponentScan(basePackageClasses = {RestfulServer.class}) 29 | @SpringBootApplication(exclude = {ElasticsearchRestClientAutoConfiguration.class, ThymeleafAutoConfiguration.class}) 30 | @Import({ 31 | StarterCrR4Config.class, 32 | StarterCrDstu3Config.class, 33 | StarterCdsHooksConfig.class, 34 | SubscriptionSubmitterConfig.class, 35 | SubscriptionProcessorConfig.class, 36 | SubscriptionChannelConfig.class, 37 | WebsocketDispatcherConfig.class, 38 | MdmConfig.class, 39 | JpaBatch2Config.class, 40 | Batch2JobsConfig.class 41 | }) 42 | public class Application extends SpringBootServletInitializer { 43 | 44 | public static void main(String[] args) { 45 | 46 | SpringApplication.run(Application.class, args); 47 | 48 | // Server is now accessible at eg. http://localhost:8080/fhir/metadata 49 | // UI is now accessible at http://localhost:8080/ 50 | } 51 | 52 | @Autowired 53 | AutowireCapableBeanFactory beanFactory; 54 | 55 | @Bean 56 | @Conditional(OnEitherVersion.class) 57 | public ServletRegistrationBean hapiServletRegistration(RestfulServer restfulServer) { 58 | ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); 59 | beanFactory.autowireBean(restfulServer); 60 | servletRegistrationBean.setServlet(restfulServer); 61 | servletRegistrationBean.addUrlMappings("/fhir/*"); 62 | servletRegistrationBean.setLoadOnStartup(1); 63 | 64 | return servletRegistrationBean; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnCorsPresent.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import org.springframework.boot.context.properties.bind.Binder; 5 | import org.springframework.context.annotation.Condition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.type.AnnotatedTypeMetadata; 8 | 9 | public class OnCorsPresent implements Condition { 10 | @Override 11 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 12 | 13 | AppProperties config = Binder.get(conditionContext.getEnvironment()) 14 | .bind("hapi.fhir", AppProperties.class) 15 | .orElse(null); 16 | if (config == null) return false; 17 | if (config.getCors() == null) return false; 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnDSTU2Condition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | public class OnDSTU2Condition implements Condition { 9 | @Override 10 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 11 | FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext 12 | .getEnvironment() 13 | .getProperty("hapi.fhir.fhir_version") 14 | .toUpperCase()); 15 | 16 | return version == FhirVersionEnum.DSTU2; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnDSTU3Condition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | public class OnDSTU3Condition implements Condition { 9 | @Override 10 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 11 | FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext 12 | .getEnvironment() 13 | .getProperty("hapi.fhir.fhir_version") 14 | .toUpperCase()); 15 | 16 | return version == FhirVersionEnum.DSTU3; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnEitherVersion.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import org.springframework.boot.autoconfigure.condition.AnyNestedCondition; 4 | import org.springframework.boot.autoconfigure.condition.ConditionOutcome; 5 | import org.springframework.context.annotation.Conditional; 6 | 7 | public class OnEitherVersion extends AnyNestedCondition { 8 | 9 | OnEitherVersion() { 10 | super(ConfigurationPhase.REGISTER_BEAN); 11 | } 12 | 13 | @Override 14 | protected ConditionOutcome getFinalMatchOutcome(MemberMatchOutcomes memberOutcomes) { 15 | ConditionOutcome result = super.getFinalMatchOutcome(memberOutcomes); 16 | return result; 17 | } 18 | 19 | @Conditional(OnDSTU2Condition.class) 20 | static class OnDSTU2 {} 21 | 22 | @Conditional(OnDSTU3Condition.class) 23 | static class OnDSTU3 {} 24 | 25 | @Conditional(OnR4Condition.class) 26 | static class OnR4 {} 27 | 28 | @Conditional(OnR4BCondition.class) 29 | static class OnR4B {} 30 | 31 | @Conditional(OnR5Condition.class) 32 | static class OnR5 {} 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnImplementationGuidesPresent.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import org.springframework.boot.context.properties.bind.Binder; 5 | import org.springframework.context.annotation.Condition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.type.AnnotatedTypeMetadata; 8 | 9 | public class OnImplementationGuidesPresent implements Condition { 10 | @Override 11 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 12 | 13 | AppProperties config = Binder.get(conditionContext.getEnvironment()) 14 | .bind("hapi.fhir", AppProperties.class) 15 | .orElse(null); 16 | if (config == null) return false; 17 | if (config.getImplementationGuides() == null) return false; 18 | return !config.getImplementationGuides().isEmpty(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR4BCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | public class OnR4BCondition implements Condition { 9 | @Override 10 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 11 | String version = conditionContext 12 | .getEnvironment() 13 | .getProperty("hapi.fhir.fhir_version") 14 | .toUpperCase(); 15 | 16 | return FhirVersionEnum.R4B.name().equals(version); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR4Condition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | public class OnR4Condition implements Condition { 9 | @Override 10 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 11 | FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext 12 | .getEnvironment() 13 | .getProperty("hapi.fhir.fhir_version") 14 | .toUpperCase()); 15 | 16 | return version == FhirVersionEnum.R4; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/annotations/OnR5Condition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.annotations; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.type.AnnotatedTypeMetadata; 7 | 8 | public class OnR5Condition implements Condition { 9 | @Override 10 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 11 | FhirVersionEnum version = FhirVersionEnum.forVersionString(conditionContext 12 | .getEnvironment() 13 | .getProperty("hapi.fhir.fhir_version") 14 | .toUpperCase()); 15 | 16 | return version == FhirVersionEnum.R5; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class CdsHooksConfigCondition implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { 11 | String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.cdshooks.enabled"); 12 | return Boolean.parseBoolean(property); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import org.springframework.boot.context.properties.ConfigurationProperties; 4 | 5 | @ConfigurationProperties(prefix = "hapi.fhir.cdshooks") 6 | public class CdsHooksProperties { 7 | 8 | private boolean enabled; 9 | 10 | public boolean isEnabled() { 11 | return enabled; 12 | } 13 | 14 | public void setEnabled(boolean enabled) { 15 | this.enabled = enabled; 16 | } 17 | 18 | private String clientIdHeaderName; 19 | 20 | public String getClientIdHeaderName() { 21 | return clientIdHeaderName; 22 | } 23 | 24 | public void setClientIdHeaderName(String clientIdHeaderName) { 25 | this.clientIdHeaderName = clientIdHeaderName; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksRequest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | 6 | @JsonIgnoreProperties({"extension"}) 7 | public class CdsHooksRequest extends CdsServiceRequestJson {} 8 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/CdsHooksServlet.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; 5 | import ca.uhn.fhir.rest.server.RestfulServer; 6 | import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 7 | import ca.uhn.hapi.fhir.cdshooks.api.ICdsServiceRegistry; 8 | import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServiceResponseJson; 9 | import ca.uhn.hapi.fhir.cdshooks.api.json.CdsServicesJson; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.google.gson.GsonBuilder; 12 | import com.google.gson.JsonParser; 13 | import jakarta.servlet.ServletException; 14 | import jakarta.servlet.http.HttpServlet; 15 | import jakarta.servlet.http.HttpServletRequest; 16 | import jakarta.servlet.http.HttpServletResponse; 17 | import org.apache.http.entity.ContentType; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.beans.factory.annotation.Configurable; 22 | import org.springframework.beans.factory.annotation.Qualifier; 23 | 24 | import java.io.IOException; 25 | import java.util.stream.Collectors; 26 | 27 | import static org.opencds.cqf.fhir.cr.hapi.config.test.TestCdsHooksConfig.CDS_HOOKS_OBJECT_MAPPER_FACTORY; 28 | 29 | @Configurable 30 | public class CdsHooksServlet extends HttpServlet { 31 | private static final Logger logger = LoggerFactory.getLogger(CdsHooksServlet.class); 32 | private static final long serialVersionUID = 1L; 33 | 34 | @Autowired 35 | private AppProperties appProperties; 36 | 37 | @Autowired 38 | private ProviderConfiguration providerConfiguration; 39 | 40 | @Autowired 41 | ICdsServiceRegistry cdsServiceRegistry; 42 | 43 | @Autowired 44 | RestfulServer restfulServer; 45 | 46 | @Autowired 47 | @Qualifier(CDS_HOOKS_OBJECT_MAPPER_FACTORY) 48 | ObjectMapper objectMapper; 49 | 50 | protected ProviderConfiguration getProviderConfiguration() { 51 | return this.providerConfiguration; 52 | } 53 | 54 | // CORS Pre-flight 55 | @Override 56 | protected void doOptions(HttpServletRequest req, HttpServletResponse resp) { 57 | ErrorHandling.setAccessControlHeaders(resp, appProperties); 58 | resp.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); 59 | resp.setHeader("X-Content-Type-Options", "nosniff"); 60 | resp.setStatus(HttpServletResponse.SC_OK); 61 | } 62 | 63 | @Override 64 | protected void doGet(HttpServletRequest request, HttpServletResponse response) 65 | throws ServletException, IOException { 66 | logger.info(request.getRequestURI()); 67 | if (!request.getRequestURL().toString().endsWith("/cds-services") 68 | && !request.getRequestURL().toString().endsWith("/cds-services/")) { 69 | logger.error(request.getRequestURI()); 70 | throw new ServletException("This servlet is not configured to handle GET requests."); 71 | } 72 | ErrorHandling.setAccessControlHeaders(response, appProperties); 73 | response.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType()); 74 | response.getWriter() 75 | .println(new GsonBuilder() 76 | .setPrettyPrinting() 77 | .create() 78 | .toJson(JsonParser.parseString(objectMapper.writeValueAsString(getServices())))); 79 | } 80 | 81 | @Override 82 | protected void doPost(HttpServletRequest request, HttpServletResponse response) 83 | throws ServletException, IOException { 84 | try { 85 | if (request.getContentType() == null || !request.getContentType().startsWith("application/json")) { 86 | throw new ServletException(String.format( 87 | "Invalid content type %s. Please use application/json.", request.getContentType())); 88 | } 89 | logger.info(request.getRequestURI()); 90 | String service = request.getPathInfo().replace("/", ""); 91 | 92 | String requestJson = request.getReader().lines().collect(Collectors.joining()); 93 | CdsHooksRequest cdsHooksRequest = objectMapper.readValue(requestJson, CdsHooksRequest.class); 94 | logRequestInfo(cdsHooksRequest, requestJson); 95 | 96 | CdsServiceResponseJson serviceResponseJson = cdsServiceRegistry.callService(service, cdsHooksRequest); 97 | 98 | // Using GSON pretty print format as Jackson's is ugly 99 | String jsonResponse = new GsonBuilder() 100 | .disableHtmlEscaping() 101 | .setPrettyPrinting() 102 | .create() 103 | .toJson(JsonParser.parseString(objectMapper.writeValueAsString(serviceResponseJson))); 104 | logger.info(jsonResponse); 105 | response.setContentType("text/json;charset=UTF-8"); 106 | response.getWriter().println(jsonResponse); 107 | } catch (BaseServerResponseException e) { 108 | ErrorHandling.handleError(response, "ERROR: Exception connecting to remote server.", e, appProperties); 109 | logger.error(e.toString()); 110 | } catch (Exception e) { 111 | logger.error(e.toString()); 112 | throw new ServletException("ERROR: Exception in cds-hooks processing.", e); 113 | } 114 | } 115 | 116 | private void logRequestInfo(CdsServiceRequestJson request, String jsonRequest) { 117 | logger.info(jsonRequest); 118 | logger.info("cds-hooks hook instance: {}", request.getHookInstance()); 119 | logger.info("cds-hooks local server address: {}", appProperties.getServer_address()); 120 | logger.info("cds-hooks fhir server address: {}", request.getFhirServer()); 121 | logger.info( 122 | "cds-hooks cql_logging_enabled: {}", 123 | this.getProviderConfiguration().getCqlLoggingEnabled()); 124 | } 125 | 126 | private CdsServicesJson getServices() { 127 | return cdsServiceRegistry.getCdsServicesJson(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ErrorHandling.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | import java.io.StringWriter; 10 | import java.util.Arrays; 11 | 12 | public class ErrorHandling { 13 | 14 | private ErrorHandling() {} 15 | 16 | public static void handleError( 17 | HttpServletResponse response, String message, Exception e, AppProperties myAppProperties) 18 | throws IOException { 19 | setAccessControlHeaders(response, myAppProperties); 20 | response.setStatus(500); // This will be overwritten with the correct status code downstream if needed. 21 | response.getWriter().println(message); 22 | printMessageAndCause(e, response); 23 | if (e instanceof BaseServerResponseException) { 24 | handleServerResponseException((BaseServerResponseException) e, response); 25 | } else if (e.getCause() instanceof BaseServerResponseException) { 26 | handleServerResponseException((BaseServerResponseException) e.getCause(), response); 27 | } 28 | printStackTrack(e, response); 29 | } 30 | 31 | private static void handleServerResponseException(BaseServerResponseException e, HttpServletResponse response) 32 | throws IOException { 33 | switch (e.getStatusCode()) { 34 | case 401: 35 | case 403: 36 | response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); 37 | response.getWriter() 38 | .println( 39 | "Ensure that the fhirAuthorization token is set or that the remote server allows unauthenticated access."); 40 | response.setStatus(412); 41 | break; 42 | case 404: 43 | response.getWriter().println("Precondition Failed. Remote FHIR server returned: " + e.getStatusCode()); 44 | response.getWriter().println("Ensure the resource exists on the remote server."); 45 | response.setStatus(412); 46 | break; 47 | default: 48 | response.getWriter().println("Unhandled Error in Remote FHIR server: " + e.getStatusCode()); 49 | } 50 | } 51 | 52 | private static void printMessageAndCause(Exception e, HttpServletResponse response) throws IOException { 53 | if (e.getMessage() != null) { 54 | response.getWriter().println(e.getMessage()); 55 | } 56 | 57 | if (e.getCause() != null && e.getCause().getMessage() != null) { 58 | response.getWriter().println(e.getCause().getMessage()); 59 | } 60 | } 61 | 62 | private static void printStackTrack(Exception e, HttpServletResponse response) throws IOException { 63 | StringWriter sw = new StringWriter(); 64 | e.printStackTrace(new PrintWriter(sw)); 65 | String exceptionAsString = sw.toString(); 66 | response.getWriter().println(exceptionAsString); 67 | } 68 | 69 | public static void setAccessControlHeaders(HttpServletResponse resp, AppProperties myAppProperties) { 70 | if (myAppProperties.getCors() != null) { 71 | if (myAppProperties.getCors().getAllow_Credentials()) { 72 | resp.setHeader( 73 | "Access-Control-Allow-Origin", 74 | myAppProperties.getCors().getAllowed_origin().stream() 75 | .findFirst() 76 | .get()); 77 | resp.setHeader( 78 | "Access-Control-Allow-Methods", 79 | String.join(", ", Arrays.asList("GET", "HEAD", "POST", "OPTIONS"))); 80 | resp.setHeader( 81 | "Access-Control-Allow-Headers", 82 | String.join( 83 | ", ", 84 | Arrays.asList( 85 | "x-fhir-starter", 86 | "Origin", 87 | "Accept", 88 | "X-Requested-With", 89 | "Content-Type", 90 | "Authorization", 91 | "Cache-Control"))); 92 | resp.setHeader( 93 | "Access-Control-Expose-Headers", 94 | String.join(", ", Arrays.asList("Location", "Content-Location"))); 95 | resp.setHeader("Access-Control-Max-Age", "86400"); 96 | } 97 | } 98 | } 99 | 100 | public static class CdsHooksError extends RuntimeException { 101 | public CdsHooksError(String message) { 102 | super(message); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/ProviderConfiguration.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.jpa.starter.cr.CrProperties; 4 | 5 | public class ProviderConfiguration { 6 | private final String clientIdHeaderName; 7 | private final boolean cqlLoggingEnabled; 8 | 9 | public ProviderConfiguration(boolean cqlLoggingEnabled, String clientIdHeaderName) { 10 | this.cqlLoggingEnabled = cqlLoggingEnabled; 11 | this.clientIdHeaderName = clientIdHeaderName; 12 | } 13 | 14 | public ProviderConfiguration(CdsHooksProperties cdsProperties, CrProperties crProperties) { 15 | this(crProperties.getCql().getRuntime().isDebugLoggingEnabled(), cdsProperties.getClientIdHeaderName()); 16 | } 17 | 18 | public String getClientIdHeaderName() { 19 | return this.clientIdHeaderName; 20 | } 21 | 22 | public boolean getCqlLoggingEnabled() { 23 | return this.cqlLoggingEnabled; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/StarterCdsHooksConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import ca.uhn.fhir.jpa.starter.cr.CrCommonConfig; 5 | import ca.uhn.fhir.jpa.starter.cr.CrConfigCondition; 6 | import ca.uhn.fhir.jpa.starter.cr.CrProperties; 7 | import ca.uhn.hapi.fhir.cdshooks.api.ICdsHooksDaoAuthorizationSvc; 8 | import ca.uhn.hapi.fhir.cdshooks.svc.CdsHooksContextBooter; 9 | import org.hl7.fhir.instance.model.api.IBaseResource; 10 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.CdsCrServiceRegistry; 11 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.CdsCrSettings; 12 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.ICdsCrServiceRegistry; 13 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.discovery.CdsCrDiscoveryServiceRegistry; 14 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.discovery.ICdsCrDiscoveryServiceRegistry; 15 | import org.opencds.cqf.fhir.cr.hapi.config.CrCdsHooksConfig; 16 | import org.opencds.cqf.fhir.cr.hapi.config.RepositoryConfig; 17 | import org.opencds.cqf.fhir.cr.hapi.config.test.TestCdsHooksConfig; 18 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 19 | import org.springframework.boot.web.servlet.ServletRegistrationBean; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Conditional; 22 | import org.springframework.context.annotation.Configuration; 23 | import org.springframework.context.annotation.Import; 24 | 25 | @Configuration 26 | @Conditional({CdsHooksConfigCondition.class, CrConfigCondition.class}) 27 | @Import({RepositoryConfig.class, TestCdsHooksConfig.class, CrCdsHooksConfig.class, CrCommonConfig.class}) 28 | public class StarterCdsHooksConfig { 29 | 30 | @Bean 31 | public ICdsCrDiscoveryServiceRegistry cdsCrDiscoveryServiceRegistry() { 32 | CdsCrDiscoveryServiceRegistry registry = new CdsCrDiscoveryServiceRegistry(); 33 | registry.unregister(FhirVersionEnum.R4); 34 | registry.register(FhirVersionEnum.R4, UpdatedCrDiscoveryService.class); 35 | return registry; 36 | } 37 | 38 | @Bean 39 | public ICdsCrServiceRegistry cdsCrServiceRegistry() { 40 | CdsCrServiceRegistry registry = new CdsCrServiceRegistry(); 41 | registry.unregister(FhirVersionEnum.R4); 42 | registry.register(FhirVersionEnum.R4, UpdatedCdsCrService.class); 43 | return registry; 44 | } 45 | 46 | @Bean 47 | public CdsHooksProperties cdsHooksProperties() { 48 | return new CdsHooksProperties(); 49 | } 50 | 51 | @Bean 52 | public CdsCrSettings cdsCrSettings(CdsHooksProperties cdsHooksProperties) { 53 | CdsCrSettings settings = CdsCrSettings.getDefault(); 54 | settings.setClientIdHeaderName(cdsHooksProperties.getClientIdHeaderName()); 55 | return settings; 56 | } 57 | 58 | @Bean 59 | public CdsHooksContextBooter cdsHooksContextBooter() { 60 | // ourLog.info("No Spring Context provided. Assuming all CDS Services will be registered dynamically."); 61 | return new CdsHooksContextBooter(); 62 | } 63 | 64 | public static class CdsHooksDaoAuthorizationSvc implements ICdsHooksDaoAuthorizationSvc { 65 | @Override 66 | public void authorizePreShow(IBaseResource theResource) {} 67 | } 68 | 69 | @Bean 70 | public ProviderConfiguration providerConfiguration(CdsHooksProperties cdsProperties, CrProperties crProperties) { 71 | return new ProviderConfiguration(cdsProperties, crProperties); 72 | } 73 | 74 | @Bean 75 | ICdsHooksDaoAuthorizationSvc cdsHooksDaoAuthorizationSvc() { 76 | return new CdsHooksDaoAuthorizationSvc(); 77 | } 78 | 79 | @Bean 80 | public ServletRegistrationBean cdsHooksRegistrationBean(AutowireCapableBeanFactory beanFactory) { 81 | CdsHooksServlet cdsHooksServlet = new CdsHooksServlet(); 82 | beanFactory.autowireBean(cdsHooksServlet); 83 | 84 | ServletRegistrationBean registrationBean = new ServletRegistrationBean<>(); 85 | registrationBean.setName("cds-hooks servlet"); 86 | registrationBean.setServlet(cdsHooksServlet); 87 | registrationBean.addUrlMappings("/cds-services/*"); 88 | registrationBean.setLoadOnStartup(1); 89 | return registrationBean; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCdsCrService.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import ca.uhn.fhir.context.FhirVersionEnum; 4 | import ca.uhn.fhir.rest.api.server.RequestDetails; 5 | import ca.uhn.fhir.rest.api.server.cdshooks.CdsServiceRequestJson; 6 | import ca.uhn.hapi.fhir.cdshooks.api.ICdsConfigService; 7 | import org.hl7.fhir.instance.model.api.IBaseParameters; 8 | import org.hl7.fhir.instance.model.api.IPrimitiveType; 9 | import org.opencds.cqf.fhir.api.Repository; 10 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.CdsCrService; 11 | import org.opencds.cqf.fhir.utility.adapter.IAdapterFactory; 12 | 13 | import static org.opencds.cqf.fhir.utility.Constants.APPLY_PARAMETER_DATA; 14 | 15 | public class UpdatedCdsCrService extends CdsCrService { 16 | private final IAdapterFactory adapterFactory; 17 | 18 | public UpdatedCdsCrService( 19 | RequestDetails theRequestDetails, Repository theRepository, ICdsConfigService theCdsConfigService) { 20 | super(theRequestDetails, theRepository, theCdsConfigService); 21 | adapterFactory = IAdapterFactory.forFhirContext(theRepository.fhirContext()); 22 | } 23 | 24 | @Override 25 | public IBaseParameters encodeParams(CdsServiceRequestJson theJson) { 26 | var parameters = adapterFactory.createParameters(super.encodeParams(theJson)); 27 | if (parameters.hasParameter(APPLY_PARAMETER_DATA)) { 28 | parameters.addParameter( 29 | "useServerData", 30 | booleanTypeForVersion(parameters.fhirContext().getVersion().getVersion(), false)); 31 | } 32 | return (IBaseParameters) parameters.get(); 33 | } 34 | 35 | private IPrimitiveType booleanTypeForVersion(FhirVersionEnum fhirVersion, boolean value) { 36 | return switch (fhirVersion) { 37 | case DSTU2 -> new org.hl7.fhir.dstu2.model.BooleanType(value); 38 | case DSTU3 -> new org.hl7.fhir.dstu3.model.BooleanType(value); 39 | case R4 -> new org.hl7.fhir.r4.model.BooleanType(value); 40 | case R5 -> new org.hl7.fhir.r5.model.BooleanType(value); 41 | default -> throw new IllegalArgumentException("unknown or unsupported FHIR version"); 42 | }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cdshooks/UpdatedCrDiscoveryService.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cdshooks; 2 | 3 | import org.hl7.fhir.instance.model.api.IIdType; 4 | import org.opencds.cqf.fhir.api.Repository; 5 | import org.opencds.cqf.fhir.cr.hapi.cdshooks.discovery.CrDiscoveryService; 6 | 7 | public class UpdatedCrDiscoveryService extends CrDiscoveryService { 8 | public UpdatedCrDiscoveryService(IIdType thePlanDefinitionId, Repository theRepository) { 9 | super(thePlanDefinitionId, theRepository); 10 | maxUriLength = 6000; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/ElasticsearchConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.search.lastn.ElasticsearchSvcImpl; 4 | import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.env.ConfigurableEnvironment; 8 | 9 | /** Shared configuration for Elasticsearch */ 10 | @Configuration 11 | public class ElasticsearchConfig { 12 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ElasticsearchConfig.class); 13 | 14 | @Bean 15 | public ElasticsearchSvcImpl elasticsearchSvc(ConfigurableEnvironment configurableEnvironment) { 16 | if (EnvironmentHelper.isElasticsearchEnabled(configurableEnvironment)) { 17 | String elasticsearchUrl = EnvironmentHelper.getElasticsearchServerUrl(configurableEnvironment); 18 | if (elasticsearchUrl.startsWith("http")) { 19 | elasticsearchUrl = elasticsearchUrl.substring(elasticsearchUrl.indexOf("://") + 3); 20 | } 21 | String elasticsearchProtocol = EnvironmentHelper.getElasticsearchServerProtocol(configurableEnvironment); 22 | String elasticsearchUsername = EnvironmentHelper.getElasticsearchServerUsername(configurableEnvironment); 23 | String elasticsearchPassword = EnvironmentHelper.getElasticsearchServerPassword(configurableEnvironment); 24 | ourLog.info("Configuring elasticsearch {} {}", elasticsearchProtocol, elasticsearchUrl); 25 | return new ElasticsearchSvcImpl( 26 | elasticsearchProtocol, elasticsearchUrl, elasticsearchUsername, elasticsearchPassword); 27 | } else { 28 | return null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigDstu2.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.config.JpaDstu2Config; 4 | import ca.uhn.fhir.jpa.starter.annotations.OnDSTU2Condition; 5 | import ca.uhn.fhir.jpa.term.TermLoaderSvcImpl; 6 | import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; 7 | import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; 8 | import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Conditional; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Import; 13 | 14 | @Configuration 15 | @Conditional(OnDSTU2Condition.class) 16 | @Import({JpaDstu2Config.class, StarterJpaConfig.class}) 17 | public class FhirServerConfigDstu2 { 18 | @Bean 19 | public ITermLoaderSvc termLoaderService( 20 | ITermDeferredStorageSvc theDeferredStorageSvc, ITermCodeSystemStorageSvc theCodeSystemStorageSvc) { 21 | return new TermLoaderSvcImpl(theDeferredStorageSvc, theCodeSystemStorageSvc); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigDstu3.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.config.dstu3.JpaDstu3Config; 4 | import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; 5 | import ca.uhn.fhir.jpa.starter.cr.StarterCrDstu3Config; 6 | import org.springframework.context.annotation.Conditional; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Conditional(OnDSTU3Condition.class) 12 | @Import({JpaDstu3Config.class, StarterJpaConfig.class, StarterCrDstu3Config.class, ElasticsearchConfig.class}) 13 | public class FhirServerConfigDstu3 {} 14 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.config.r4.JpaR4Config; 4 | import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; 5 | import ca.uhn.fhir.jpa.starter.cr.StarterCrR4Config; 6 | import ca.uhn.fhir.jpa.starter.ips.StarterIpsConfig; 7 | import org.springframework.context.annotation.Conditional; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | @Configuration 12 | @Conditional(OnR4Condition.class) 13 | @Import({ 14 | JpaR4Config.class, 15 | StarterJpaConfig.class, 16 | StarterCrR4Config.class, 17 | ElasticsearchConfig.class, 18 | StarterIpsConfig.class 19 | }) 20 | public class FhirServerConfigR4 {} 21 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR4B.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.config.r4b.JpaR4BConfig; 4 | import ca.uhn.fhir.jpa.starter.annotations.OnR4BCondition; 5 | import ca.uhn.fhir.jpa.topic.SubscriptionTopicConfig; 6 | import org.springframework.context.annotation.Conditional; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Conditional(OnR4BCondition.class) 12 | @Import({JpaR4BConfig.class, SubscriptionTopicConfig.class, StarterJpaConfig.class, ElasticsearchConfig.class}) 13 | public class FhirServerConfigR4B {} 14 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirServerConfigR5.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.config.r5.JpaR5Config; 4 | import ca.uhn.fhir.jpa.starter.annotations.OnR5Condition; 5 | import ca.uhn.fhir.jpa.topic.SubscriptionTopicConfig; 6 | import org.springframework.context.annotation.Conditional; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Conditional(OnR5Condition.class) 12 | @Import({StarterJpaConfig.class, JpaR5Config.class, SubscriptionTopicConfig.class, ElasticsearchConfig.class}) 13 | public class FhirServerConfigR5 {} 14 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirTesterConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import ca.uhn.fhir.to.FhirTesterMvcConfig; 5 | import ca.uhn.fhir.to.TesterConfig; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Conditional; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | // @formatter:off 12 | /** 13 | * This spring config file configures the web testing module. It serves two 14 | * purposes: 15 | * 1. It imports FhirTesterMvcConfig, which is the spring config for the 16 | * tester itself 17 | * 2. It tells the tester which server(s) to talk to, via the testerConfig() 18 | * method below 19 | */ 20 | @Configuration 21 | @Import(FhirTesterMvcConfig.class) 22 | @Conditional(FhirTesterConfigCondition.class) 23 | public class FhirTesterConfig { 24 | 25 | /** 26 | * This bean tells the testing webpage which servers it should configure itself 27 | * to communicate with. In this example we configure it to talk to the local 28 | * server, as well as one public server. If you are creating a project to 29 | * deploy somewhere else, you might choose to only put your own server's 30 | * address here. 31 | *

32 | * Note the use of the ${serverBase} variable below. This will be replaced with 33 | * the base URL as reported by the server itself. Often for a simple Tomcat 34 | * (or other container) installation, this will end up being something 35 | * like "http://localhost:8080/hapi-fhir-jpaserver-starter". If you are 36 | * deploying your server to a place with a fully qualified domain name, 37 | * you might want to use that instead of using the variable. 38 | */ 39 | @Bean 40 | public TesterConfig testerConfig(AppProperties appProperties) { 41 | TesterConfig retVal = new TesterConfig(); 42 | appProperties.getTester().forEach((key, value) -> { 43 | retVal.addServer() 44 | .withId(key) 45 | .withFhirVersion(value.getFhir_version()) 46 | .withBaseUrl(value.getServer_address()) 47 | .withName(value.getName()); 48 | retVal.setRefuseToFetchThirdPartyUrls(value.getRefuse_to_fetch_third_party_urls()); 49 | }); 50 | return retVal; 51 | } 52 | } 53 | // @formatter:on 54 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/FhirTesterConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper; 4 | import org.springframework.context.annotation.Condition; 5 | import org.springframework.context.annotation.ConditionContext; 6 | import org.springframework.core.env.ConfigurableEnvironment; 7 | import org.springframework.core.type.AnnotatedTypeMetadata; 8 | 9 | public class FhirTesterConfigCondition implements Condition { 10 | @Override 11 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 12 | 13 | var properties = EnvironmentHelper.getPropertiesStartingWith( 14 | (ConfigurableEnvironment) conditionContext.getEnvironment(), "hapi.fhir.tester"); 15 | return !properties.isEmpty(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/PartitionModeConfigurer.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor; 5 | import ca.uhn.fhir.jpa.model.config.PartitionSettings; 6 | import ca.uhn.fhir.jpa.partition.PartitionManagementProvider; 7 | import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; 8 | import ca.uhn.fhir.jpa.starter.AppProperties; 9 | import ca.uhn.fhir.rest.server.RestfulServer; 10 | import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor; 11 | import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy; 12 | import jakarta.annotation.PostConstruct; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | 17 | public class PartitionModeConfigurer { 18 | private static final Logger ourLog = LoggerFactory.getLogger(PartitionModeConfigurer.class); 19 | 20 | @Autowired 21 | private AppProperties myAppProperties; 22 | 23 | @Autowired 24 | private FhirContext myFhirContext; 25 | 26 | @Autowired 27 | private ISearchParamExtractor mySearchParamExtractor; 28 | 29 | @Autowired 30 | private PartitionSettings myPartitionSettings; 31 | 32 | @Autowired 33 | private RestfulServer myRestfulServer; 34 | 35 | @Autowired 36 | private PartitionManagementProvider myPartitionManagementProvider; 37 | 38 | @PostConstruct 39 | public void start() { 40 | if (myAppProperties.getPartitioning() != null) { 41 | if (myAppProperties.getPartitioning().getPatient_id_partitioning_mode() == Boolean.TRUE) { 42 | ourLog.info("Partitioning mode enabled in: Patient ID partitioning mode"); 43 | PatientIdPartitionInterceptor patientIdInterceptor = 44 | new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings); 45 | myRestfulServer.registerInterceptor(patientIdInterceptor); 46 | myPartitionSettings.setUnnamedPartitionMode(true); 47 | } else if (myAppProperties.getPartitioning().getRequest_tenant_partitioning_mode() == Boolean.TRUE) { 48 | ourLog.info("Partitioning mode enabled in: Request tenant partitioning mode"); 49 | myRestfulServer.registerInterceptor(new RequestTenantPartitionInterceptor()); 50 | myRestfulServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy()); 51 | } 52 | 53 | myRestfulServer.registerProviders(myPartitionManagementProvider); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/IRepositoryValidationInterceptorFactory.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; 4 | 5 | public interface IRepositoryValidationInterceptorFactory { 6 | 7 | String ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR = "enable_repository_validating_interceptor"; 8 | 9 | RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions(); 10 | 11 | RepositoryValidatingInterceptor build(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/OnRemoteTerminologyPresent.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import org.springframework.boot.context.properties.bind.Binder; 5 | import org.springframework.context.annotation.Condition; 6 | import org.springframework.context.annotation.ConditionContext; 7 | import org.springframework.core.type.AnnotatedTypeMetadata; 8 | 9 | public class OnRemoteTerminologyPresent implements Condition { 10 | @Override 11 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 12 | 13 | AppProperties config = Binder.get(conditionContext.getEnvironment()) 14 | .bind("hapi.fhir", AppProperties.class) 15 | .orElse(null); 16 | if (config == null) return false; 17 | if (config.getRemoteTerminologyServicesMap() == null) return false; 18 | return !config.getRemoteTerminologyServicesMap().isEmpty(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryDstu3.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 5 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 6 | import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule; 7 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; 8 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder; 9 | import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 10 | import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; 11 | import ca.uhn.fhir.rest.api.server.IBundleProvider; 12 | import ca.uhn.fhir.rest.param.TokenParam; 13 | import org.hl7.fhir.dstu3.model.StructureDefinition; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.context.annotation.Conditional; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; 23 | 24 | /** 25 | * This class can be customized to enable the {@link RepositoryValidatingInterceptor} 26 | * on this server. 27 | *

28 | * The enable_repository_validating_interceptor property must be enabled in application.yaml 29 | * in order to use this class. 30 | */ 31 | @ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true") 32 | @Configuration 33 | @Conditional(OnDSTU3Condition.class) 34 | public class RepositoryValidationInterceptorFactoryDstu3 implements IRepositoryValidationInterceptorFactory { 35 | 36 | private final FhirContext fhirContext; 37 | private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; 38 | private final IFhirResourceDao structureDefinitionResourceProvider; 39 | 40 | public RepositoryValidationInterceptorFactoryDstu3( 41 | RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { 42 | this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; 43 | this.fhirContext = daoRegistry.getSystemDao().getContext(); 44 | structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); 45 | } 46 | 47 | public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { 48 | 49 | IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() 50 | .setLoadSynchronous(true) 51 | .add(StructureDefinition.SP_KIND, new TokenParam("resource"))); 52 | Map> structureDefinitions = results.getResources(0, results.size()).stream() 53 | .map(StructureDefinition.class::cast) 54 | .collect(Collectors.groupingBy(StructureDefinition::getType)); 55 | 56 | structureDefinitions.forEach((key, value) -> { 57 | String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); 58 | repositoryValidatingRuleBuilder 59 | .forResourcesOfType(key) 60 | .requireAtLeastOneProfileOf(urls) 61 | .and() 62 | .requireValidationToDeclaredProfiles(); 63 | }); 64 | 65 | List rules = repositoryValidatingRuleBuilder.build(); 66 | return new RepositoryValidatingInterceptor(fhirContext, rules); 67 | } 68 | 69 | public RepositoryValidatingInterceptor build() { 70 | 71 | // Customize the ruleBuilder here to have the rules you want! We will give a simple example 72 | // of enabling validation for all Patient resources 73 | repositoryValidatingRuleBuilder 74 | .forResourcesOfType("Patient") 75 | .requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient") 76 | .and() 77 | .requireValidationToDeclaredProfiles(); 78 | 79 | // Do not customize below this line 80 | List rules = repositoryValidatingRuleBuilder.build(); 81 | return new RepositoryValidatingInterceptor(fhirContext, rules); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR4.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 5 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 6 | import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule; 7 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; 8 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder; 9 | import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 10 | import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; 11 | import ca.uhn.fhir.rest.api.server.IBundleProvider; 12 | import ca.uhn.fhir.rest.param.TokenParam; 13 | import org.hl7.fhir.r4.model.StructureDefinition; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.context.annotation.Conditional; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; 23 | 24 | /** 25 | * This class can be customized to enable the {@link ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor} 26 | * on this server. 27 | *

28 | * The enable_repository_validating_interceptor property must be enabled in application.yaml 29 | * in order to use this class. 30 | */ 31 | @ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true") 32 | @Configuration 33 | @Conditional(OnR4Condition.class) 34 | public class RepositoryValidationInterceptorFactoryR4 implements IRepositoryValidationInterceptorFactory { 35 | 36 | private final FhirContext fhirContext; 37 | private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; 38 | private final IFhirResourceDao structureDefinitionResourceProvider; 39 | 40 | public RepositoryValidationInterceptorFactoryR4( 41 | RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { 42 | this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; 43 | this.fhirContext = daoRegistry.getSystemDao().getContext(); 44 | structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); 45 | } 46 | 47 | @Override 48 | public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { 49 | 50 | IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() 51 | .setLoadSynchronous(true) 52 | .add(StructureDefinition.SP_KIND, new TokenParam("resource"))); 53 | Map> structureDefintions = results.getResources(0, results.size()).stream() 54 | .map(StructureDefinition.class::cast) 55 | .collect(Collectors.groupingBy(StructureDefinition::getType)); 56 | 57 | structureDefintions.forEach((key, value) -> { 58 | String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); 59 | repositoryValidatingRuleBuilder 60 | .forResourcesOfType(key) 61 | .requireAtLeastOneProfileOf(urls) 62 | .and() 63 | .requireValidationToDeclaredProfiles(); 64 | }); 65 | 66 | List rules = repositoryValidatingRuleBuilder.build(); 67 | return new RepositoryValidatingInterceptor(fhirContext, rules); 68 | } 69 | 70 | @Override 71 | public RepositoryValidatingInterceptor build() { 72 | 73 | // Customize the ruleBuilder here to have the rules you want! We will give a simple example 74 | // of enabling validation for all Patient resources 75 | repositoryValidatingRuleBuilder 76 | .forResourcesOfType("Patient") 77 | .requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient") 78 | .and() 79 | .requireValidationToDeclaredProfiles(); 80 | 81 | // Do not customize below this line 82 | List rules = repositoryValidatingRuleBuilder.build(); 83 | return new RepositoryValidatingInterceptor(fhirContext, rules); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR4B.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 5 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 6 | import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule; 7 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; 8 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder; 9 | import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 10 | import ca.uhn.fhir.jpa.starter.annotations.OnR4BCondition; 11 | import ca.uhn.fhir.rest.api.server.IBundleProvider; 12 | import ca.uhn.fhir.rest.param.TokenParam; 13 | import org.hl7.fhir.r4b.model.StructureDefinition; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.context.annotation.Conditional; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; 23 | 24 | /** 25 | * This class can be customized to enable the {@link RepositoryValidatingInterceptor} 26 | * on this server. 27 | *

28 | * The enable_repository_validating_interceptor property must be enabled in application.yaml 29 | * in order to use this class. 30 | */ 31 | @ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true") 32 | @Configuration 33 | @Conditional(OnR4BCondition.class) 34 | public class RepositoryValidationInterceptorFactoryR4B implements IRepositoryValidationInterceptorFactory { 35 | 36 | private final FhirContext fhirContext; 37 | private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; 38 | private final IFhirResourceDao structureDefinitionResourceProvider; 39 | 40 | public RepositoryValidationInterceptorFactoryR4B( 41 | RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { 42 | this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; 43 | this.fhirContext = daoRegistry.getSystemDao().getContext(); 44 | structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); 45 | } 46 | 47 | @Override 48 | public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { 49 | 50 | IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() 51 | .setLoadSynchronous(true) 52 | .add(StructureDefinition.SP_KIND, new TokenParam("resource"))); 53 | Map> structureDefintions = results.getResources(0, results.size()).stream() 54 | .map(StructureDefinition.class::cast) 55 | .collect(Collectors.groupingBy(StructureDefinition::getType)); 56 | 57 | structureDefintions.forEach((key, value) -> { 58 | String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); 59 | repositoryValidatingRuleBuilder 60 | .forResourcesOfType(key) 61 | .requireAtLeastOneProfileOf(urls) 62 | .and() 63 | .requireValidationToDeclaredProfiles(); 64 | }); 65 | 66 | List rules = repositoryValidatingRuleBuilder.build(); 67 | return new RepositoryValidatingInterceptor(fhirContext, rules); 68 | } 69 | 70 | @Override 71 | public RepositoryValidatingInterceptor build() { 72 | 73 | // Customize the ruleBuilder here to have the rules you want! We will give a simple example 74 | // of enabling validation for all Patient resources 75 | repositoryValidatingRuleBuilder 76 | .forResourcesOfType("Patient") 77 | .requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient") 78 | .and() 79 | .requireValidationToDeclaredProfiles(); 80 | 81 | // Do not customize below this line 82 | List rules = repositoryValidatingRuleBuilder.build(); 83 | return new RepositoryValidatingInterceptor(fhirContext, rules); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/common/validation/RepositoryValidationInterceptorFactoryR5.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.common.validation; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 5 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 6 | import ca.uhn.fhir.jpa.interceptor.validation.IRepositoryValidatingRule; 7 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingInterceptor; 8 | import ca.uhn.fhir.jpa.interceptor.validation.RepositoryValidatingRuleBuilder; 9 | import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; 10 | import ca.uhn.fhir.jpa.starter.annotations.OnR5Condition; 11 | import ca.uhn.fhir.rest.api.server.IBundleProvider; 12 | import ca.uhn.fhir.rest.param.TokenParam; 13 | import org.hl7.fhir.r5.model.StructureDefinition; 14 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 15 | import org.springframework.context.annotation.Conditional; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.stream.Collectors; 21 | 22 | import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR; 23 | 24 | /** 25 | * This class can be customized to enable the {@link RepositoryValidatingInterceptor} 26 | * on this server. 27 | *

28 | * The enable_repository_validating_interceptor property must be enabled in application.yaml 29 | * in order to use this class. 30 | */ 31 | @ConditionalOnProperty(prefix = "hapi.fhir", name = ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR, havingValue = "true") 32 | @Configuration 33 | @Conditional(OnR5Condition.class) 34 | public class RepositoryValidationInterceptorFactoryR5 implements IRepositoryValidationInterceptorFactory { 35 | 36 | private final FhirContext fhirContext; 37 | private final RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder; 38 | private final IFhirResourceDao structureDefinitionResourceProvider; 39 | 40 | public RepositoryValidationInterceptorFactoryR5( 41 | RepositoryValidatingRuleBuilder repositoryValidatingRuleBuilder, DaoRegistry daoRegistry) { 42 | this.repositoryValidatingRuleBuilder = repositoryValidatingRuleBuilder; 43 | this.fhirContext = daoRegistry.getSystemDao().getContext(); 44 | structureDefinitionResourceProvider = daoRegistry.getResourceDao("StructureDefinition"); 45 | } 46 | 47 | public RepositoryValidatingInterceptor buildUsingStoredStructureDefinitions() { 48 | 49 | IBundleProvider results = structureDefinitionResourceProvider.search(new SearchParameterMap() 50 | .setLoadSynchronous(true) 51 | .add(StructureDefinition.SP_KIND, new TokenParam("resource"))); 52 | Map> structureDefintions = results.getResources(0, results.size()).stream() 53 | .map(StructureDefinition.class::cast) 54 | .collect(Collectors.groupingBy(StructureDefinition::getType)); 55 | 56 | structureDefintions.forEach((key, value) -> { 57 | String[] urls = value.stream().map(StructureDefinition::getUrl).toArray(String[]::new); 58 | repositoryValidatingRuleBuilder 59 | .forResourcesOfType(key) 60 | .requireAtLeastOneProfileOf(urls) 61 | .and() 62 | .requireValidationToDeclaredProfiles(); 63 | }); 64 | 65 | List rules = repositoryValidatingRuleBuilder.build(); 66 | return new RepositoryValidatingInterceptor(fhirContext, rules); 67 | } 68 | 69 | public RepositoryValidatingInterceptor build() { 70 | 71 | // Customize the ruleBuilder here to have the rules you want! We will give a simple example 72 | // of enabling validation for all Patient resources 73 | repositoryValidatingRuleBuilder 74 | .forResourcesOfType("Patient") 75 | .requireAtLeastProfile("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient") 76 | .and() 77 | .requireValidationToDeclaredProfiles(); 78 | 79 | // Do not customize below this line 80 | List rules = repositoryValidatingRuleBuilder.build(); 81 | return new RepositoryValidatingInterceptor(fhirContext, rules); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CareGapsProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | public class CareGapsProperties { 4 | private String reporter = "default"; 5 | private String section_author = "default"; 6 | 7 | public String getReporter() { 8 | return reporter; 9 | } 10 | 11 | public void setReporter(String reporter) { 12 | this.reporter = reporter; 13 | } 14 | 15 | public String getSection_author() { 16 | return section_author; 17 | } 18 | 19 | public void setSection_author(String section_author) { 20 | this.section_author = section_author; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlCompilerProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import org.cqframework.cql.cql2elm.CqlCompilerException; 4 | import org.cqframework.cql.cql2elm.CqlTranslator; 5 | import org.cqframework.cql.cql2elm.LibraryBuilder; 6 | 7 | public class CqlCompilerProperties { 8 | private Boolean validate_units = true; 9 | private Boolean verify_only = false; 10 | private String compatibility_level = "1.5"; 11 | private CqlCompilerException.ErrorSeverity error_level = CqlCompilerException.ErrorSeverity.Info; 12 | private LibraryBuilder.SignatureLevel signature_level = LibraryBuilder.SignatureLevel.All; 13 | private Boolean analyze_data_requirements = false; 14 | private Boolean collapse_data_requirements = false; 15 | private CqlTranslator.Format translator_format = CqlTranslator.Format.JSON; 16 | private Boolean enable_date_range_optimization = true; 17 | private Boolean enable_annotations = true; 18 | private Boolean enable_locators = true; 19 | private Boolean enable_results_type = true; 20 | private Boolean enable_detailed_errors = true; 21 | private Boolean disable_list_traversal = false; 22 | private Boolean disable_list_demotion = false; 23 | private Boolean disable_list_promotion = false; 24 | private Boolean enable_interval_demotion = false; 25 | private Boolean enable_interval_promotion = false; 26 | private Boolean disable_method_invocation = false; 27 | private Boolean require_from_keyword = false; 28 | private Boolean disable_default_model_info_load = false; 29 | 30 | public boolean isValidateUnits() { 31 | return validate_units; 32 | } 33 | 34 | public void setValidateUnits(boolean validateUnits) { 35 | this.validate_units = validateUnits; 36 | } 37 | 38 | public boolean isVerifyOnly() { 39 | return verify_only; 40 | } 41 | 42 | public void setVerifyOnly(boolean verifyOnly) { 43 | this.verify_only = verifyOnly; 44 | } 45 | 46 | public String getCompatibilityLevel() { 47 | return compatibility_level; 48 | } 49 | 50 | public void setCompatibilityLevel(String compatibilityLevel) { 51 | this.compatibility_level = compatibilityLevel; 52 | } 53 | 54 | public CqlCompilerException.ErrorSeverity getErrorSeverityLevel() { 55 | return error_level; 56 | } 57 | 58 | public void setErrorSeverityLevel(CqlCompilerException.ErrorSeverity errorSeverityLevel) { 59 | this.error_level = errorSeverityLevel; 60 | } 61 | 62 | public LibraryBuilder.SignatureLevel getSignatureLevel() { 63 | return signature_level; 64 | } 65 | 66 | public void setSignatureLevel(LibraryBuilder.SignatureLevel signatureLevel) { 67 | this.signature_level = signatureLevel; 68 | } 69 | 70 | public boolean isAnalyzeDataRequirements() { 71 | return analyze_data_requirements; 72 | } 73 | 74 | public void setAnalyzeDataRequirements(boolean analyzeDataRequirements) { 75 | this.analyze_data_requirements = analyzeDataRequirements; 76 | } 77 | 78 | public boolean isCollapseDataRequirements() { 79 | return collapse_data_requirements; 80 | } 81 | 82 | public void setCollapseDataRequirements(boolean collapseDataRequirements) { 83 | this.collapse_data_requirements = collapseDataRequirements; 84 | } 85 | 86 | public boolean isEnableDateRangeOptimization() { 87 | return enable_date_range_optimization; 88 | } 89 | 90 | public void setEnableDateRangeOptimization(boolean enableDateRangeOptimization) { 91 | this.enable_date_range_optimization = enableDateRangeOptimization; 92 | } 93 | 94 | public boolean isEnableAnnotations() { 95 | return enable_annotations; 96 | } 97 | 98 | public void setEnableAnnotations(boolean enableAnnotations) { 99 | this.enable_annotations = enableAnnotations; 100 | } 101 | 102 | public boolean isEnableLocators() { 103 | return enable_locators; 104 | } 105 | 106 | public void setEnableLocators(boolean enableLocators) { 107 | this.enable_locators = enableLocators; 108 | } 109 | 110 | public boolean isEnableResultsType() { 111 | return enable_results_type; 112 | } 113 | 114 | public void setEnableResultsType(boolean enableResultsType) { 115 | this.enable_results_type = enableResultsType; 116 | } 117 | 118 | public boolean isEnableDetailedErrors() { 119 | return enable_detailed_errors; 120 | } 121 | 122 | public void setEnableDetailedErrors(boolean enableDetailedErrors) { 123 | this.enable_detailed_errors = enableDetailedErrors; 124 | } 125 | 126 | public boolean isDisableListTraversal() { 127 | return disable_list_traversal; 128 | } 129 | 130 | public void setDisableListTraversal(boolean disableListTraversal) { 131 | this.disable_list_traversal = disableListTraversal; 132 | } 133 | 134 | public boolean isDisableListDemotion() { 135 | return disable_list_demotion; 136 | } 137 | 138 | public void setDisableListDemotion(boolean disableListDemotion) { 139 | this.disable_list_demotion = disableListDemotion; 140 | } 141 | 142 | public boolean isDisableListPromotion() { 143 | return disable_list_promotion; 144 | } 145 | 146 | public void setDisableListPromotion(boolean disableListPromotion) { 147 | this.disable_list_promotion = disableListPromotion; 148 | } 149 | 150 | public boolean isEnableIntervalPromotion() { 151 | return enable_interval_promotion; 152 | } 153 | 154 | public void setEnableIntervalPromotion(boolean enableIntervalPromotion) { 155 | this.enable_interval_promotion = enableIntervalPromotion; 156 | } 157 | 158 | public boolean isEnableIntervalDemotion() { 159 | return enable_interval_demotion; 160 | } 161 | 162 | public void setEnableIntervalDemotion(boolean enableIntervalDemotion) { 163 | this.enable_interval_demotion = enableIntervalDemotion; 164 | } 165 | 166 | public boolean isDisableMethodInvocation() { 167 | return disable_method_invocation; 168 | } 169 | 170 | public void setDisableMethodInvocation(boolean disableMethodInvocation) { 171 | this.disable_method_invocation = disableMethodInvocation; 172 | } 173 | 174 | public boolean isRequireFromKeyword() { 175 | return require_from_keyword; 176 | } 177 | 178 | public void setRequireFromKeyword(boolean requireFromKeyword) { 179 | this.require_from_keyword = requireFromKeyword; 180 | } 181 | 182 | public boolean isDisableDefaultModelInfoLoad() { 183 | return disable_default_model_info_load; 184 | } 185 | 186 | public void setDisableDefaultModelInfoLoad(boolean disableDefaultModelInfoLoad) { 187 | this.disable_default_model_info_load = disableDefaultModelInfoLoad; 188 | } 189 | 190 | public CqlTranslator.Format getTranslatorFormat() { 191 | return translator_format; 192 | } 193 | 194 | public void setTranslatorFormat(CqlTranslator.Format translatorFormat) { 195 | this.translator_format = translatorFormat; 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import org.opencds.cqf.fhir.cql.engine.retrieve.RetrieveSettings; 4 | import org.opencds.cqf.fhir.cql.engine.terminology.TerminologySettings; 5 | 6 | public class CqlProperties { 7 | 8 | private Boolean use_embedded_libraries = true; 9 | private CqlCompilerProperties compiler = new CqlCompilerProperties(); 10 | private CqlRuntimeProperties runtime = new CqlRuntimeProperties(); 11 | private TerminologySettings terminology = new TerminologySettings(); 12 | private RetrieveSettings data = new RetrieveSettings(); 13 | 14 | public Boolean getUse_embedded_libraries() { 15 | return use_embedded_libraries; 16 | } 17 | 18 | public void setUse_embedded_libraries(Boolean use_embedded_libraries) { 19 | this.use_embedded_libraries = use_embedded_libraries; 20 | } 21 | 22 | public CqlCompilerProperties getCompiler() { 23 | return compiler; 24 | } 25 | 26 | public void setCompiler(CqlCompilerProperties compiler) { 27 | this.compiler = compiler; 28 | } 29 | 30 | public CqlRuntimeProperties getRuntime() { 31 | return runtime; 32 | } 33 | 34 | public void setRuntime(CqlRuntimeProperties runtime) { 35 | this.runtime = runtime; 36 | } 37 | 38 | public TerminologySettings getTerminology() { 39 | return terminology; 40 | } 41 | 42 | public void setTerminology(TerminologySettings terminology) { 43 | this.terminology = terminology; 44 | } 45 | 46 | public RetrieveSettings getData() { 47 | return data; 48 | } 49 | 50 | public void setData(RetrieveSettings data) { 51 | this.data = data; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CqlRuntimeProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | public class CqlRuntimeProperties { 4 | 5 | private Boolean debug_logging_enabled = false; 6 | private Boolean enable_validation = false; 7 | private Boolean enable_expression_caching = true; 8 | 9 | public boolean isDebugLoggingEnabled() { 10 | return debug_logging_enabled; 11 | } 12 | 13 | public void setDebugLoggingEnabled(boolean debug_logging_enabled) { 14 | this.debug_logging_enabled = debug_logging_enabled; 15 | } 16 | 17 | public boolean isEnableExpressionCaching() { 18 | return enable_expression_caching; 19 | } 20 | 21 | public void setEnableExpressionCaching(boolean enable_expression_caching) { 22 | this.enable_expression_caching = enable_expression_caching; 23 | } 24 | 25 | public boolean isEnableValidation() { 26 | return enable_validation; 27 | } 28 | 29 | public void EnableValidation(boolean enable_validation) { 30 | this.enable_validation = enable_validation; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CrConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class CrConfigCondition implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { 11 | String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.cr.enabled"); 12 | return Boolean.parseBoolean(property); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/CrProperties.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | public class CrProperties { 4 | private Boolean enabled; 5 | 6 | private CareGapsProperties careGaps = new CareGapsProperties(); 7 | private CqlProperties cql = new CqlProperties(); 8 | 9 | public Boolean getEnabled() { 10 | return enabled; 11 | } 12 | 13 | public void setEnabled(Boolean enabled) { 14 | this.enabled = enabled; 15 | } 16 | 17 | public CareGapsProperties getCareGaps() { 18 | return careGaps; 19 | } 20 | 21 | public void setCareGaps(CareGapsProperties careGaps) { 22 | this.careGaps = careGaps; 23 | } 24 | 25 | public CqlProperties getCql() { 26 | return cql; 27 | } 28 | 29 | public void setCql(CqlProperties cql) { 30 | this.cql = cql; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/PostInitProviderRegisterer.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import ca.uhn.fhir.rest.server.RestfulServer; 4 | import ca.uhn.fhir.rest.server.provider.IResourceProviderFactoryObserver; 5 | import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; 6 | 7 | import java.util.function.Supplier; 8 | 9 | public class PostInitProviderRegisterer { 10 | public PostInitProviderRegisterer(RestfulServer restfulServer, ResourceProviderFactory resourceProviderFactory) { 11 | resourceProviderFactory.attach(new Observer(restfulServer)); 12 | } 13 | 14 | private class Observer implements IResourceProviderFactoryObserver { 15 | private RestfulServer restfulServer; 16 | 17 | public Observer(RestfulServer restfulServer) { 18 | this.restfulServer = restfulServer; 19 | } 20 | 21 | public void update(Supplier theSupplier) { 22 | if (theSupplier == null) { 23 | return; 24 | } 25 | 26 | var provider = theSupplier.get(); 27 | if (provider == null) { 28 | return; 29 | } 30 | 31 | this.restfulServer.registerProvider(provider); 32 | } 33 | 34 | public void remove(Supplier theSupplier) { 35 | if (theSupplier == null) { 36 | return; 37 | } 38 | 39 | var provider = theSupplier.get(); 40 | if (provider == null) { 41 | return; 42 | } 43 | 44 | this.restfulServer.unregisterProvider(provider); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrDstu3Config.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import ca.uhn.fhir.jpa.starter.annotations.OnDSTU3Condition; 4 | import org.opencds.cqf.fhir.cr.hapi.config.dstu3.ApplyOperationConfig; 5 | import org.opencds.cqf.fhir.cr.hapi.config.dstu3.CrDstu3Config; 6 | import org.opencds.cqf.fhir.cr.hapi.config.dstu3.DataRequirementsOperationConfig; 7 | import org.opencds.cqf.fhir.cr.hapi.config.dstu3.EvaluateOperationConfig; 8 | import org.opencds.cqf.fhir.cr.hapi.config.dstu3.PackageOperationConfig; 9 | import org.springframework.context.annotation.*; 10 | 11 | @Configuration 12 | @Conditional({OnDSTU3Condition.class, CrConfigCondition.class}) 13 | @Import({ 14 | CrCommonConfig.class, 15 | CrDstu3Config.class, 16 | ApplyOperationConfig.class, 17 | DataRequirementsOperationConfig.class, 18 | EvaluateOperationConfig.class, 19 | PackageOperationConfig.class 20 | }) 21 | public class StarterCrDstu3Config {} 22 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/cr/StarterCrR4Config.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.cr; 2 | 3 | import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; 4 | import org.opencds.cqf.fhir.cr.hapi.config.r4.ApplyOperationConfig; 5 | import org.opencds.cqf.fhir.cr.hapi.config.r4.CrR4Config; 6 | import org.opencds.cqf.fhir.cr.hapi.config.r4.DataRequirementsOperationConfig; 7 | import org.opencds.cqf.fhir.cr.hapi.config.r4.EvaluateOperationConfig; 8 | import org.opencds.cqf.fhir.cr.hapi.config.r4.ExtractOperationConfig; 9 | import org.opencds.cqf.fhir.cr.hapi.config.r4.PackageOperationConfig; 10 | import org.opencds.cqf.fhir.cr.hapi.config.r4.PopulateOperationConfig; 11 | import org.opencds.cqf.fhir.cr.hapi.config.r4.QuestionnaireOperationConfig; 12 | import org.springframework.context.annotation.Conditional; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Import; 15 | 16 | @Configuration 17 | @Conditional({OnR4Condition.class, CrConfigCondition.class}) 18 | @Import({ 19 | CrCommonConfig.class, 20 | CrR4Config.class, 21 | ApplyOperationConfig.class, 22 | DataRequirementsOperationConfig.class, 23 | EvaluateOperationConfig.class, 24 | ExtractOperationConfig.class, 25 | PackageOperationConfig.class, 26 | PopulateOperationConfig.class, 27 | QuestionnaireOperationConfig.class 28 | }) 29 | public class StarterCrR4Config {} 30 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ig/IImplementationGuideOperationProvider.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ig; 2 | 3 | import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; 4 | import org.hl7.fhir.utilities.npm.NpmPackage; 5 | 6 | import java.io.ByteArrayInputStream; 7 | import java.io.IOException; 8 | 9 | public interface IImplementationGuideOperationProvider { 10 | static PackageInstallationSpec toPackageInstallationSpec(byte[] npmPackageAsByteArray) throws IOException { 11 | NpmPackage npmPackage = NpmPackage.fromPackage(new ByteArrayInputStream(npmPackageAsByteArray)); 12 | return new PackageInstallationSpec() 13 | .setName(npmPackage.name()) 14 | .setPackageContents(npmPackageAsByteArray) 15 | .setVersion(npmPackage.version()) 16 | .setInstallMode(PackageInstallationSpec.InstallModeEnum.STORE_AND_INSTALL) 17 | .setFetchDependencies(false); 18 | } 19 | 20 | // The following declaration is the one that counts but cannot be used across different versions as stating 21 | // Base64BinaryType would bind to a separate version 22 | // Parameters install(@OperationParam(name = "npmContent",min = 1, max = 1) Base64BinaryType implementationGuide); 23 | // Parameters uninstall(@OperationParam(name = "name", min = 1, max = 1) String name, @OperationParam(name = 24 | // "version", min = 1, max = 1) String version) ; 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ig/IgConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ig; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class IgConfigCondition implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { 11 | String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ig_runtime_upload_enabled"); 12 | return Boolean.parseBoolean(property); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ig/ImplementationGuideR4OperationProvider.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ig; 2 | 3 | import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; 4 | import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; 5 | import ca.uhn.fhir.jpa.starter.annotations.OnR4Condition; 6 | import ca.uhn.fhir.rest.annotation.Operation; 7 | import ca.uhn.fhir.rest.annotation.OperationParam; 8 | import org.hl7.fhir.r4.model.Base64BinaryType; 9 | import org.hl7.fhir.r4.model.Parameters; 10 | import org.springframework.context.annotation.Conditional; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.io.IOException; 14 | 15 | @Conditional({OnR4Condition.class, IgConfigCondition.class}) 16 | @Service 17 | public class ImplementationGuideR4OperationProvider implements IImplementationGuideOperationProvider { 18 | 19 | IPackageInstallerSvc packageInstallerSvc; 20 | 21 | public ImplementationGuideR4OperationProvider(IPackageInstallerSvc packageInstallerSvc) { 22 | this.packageInstallerSvc = packageInstallerSvc; 23 | } 24 | 25 | @Operation(name = "$install", typeName = "ImplementationGuide") 26 | public Parameters install( 27 | @OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) { 28 | try { 29 | 30 | packageInstallerSvc.install( 31 | IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue())); 32 | } catch (IOException e) { 33 | throw new RuntimeException(e); 34 | } 35 | return new Parameters(); 36 | } 37 | 38 | @Operation(name = "$uninstall", typeName = "ImplementationGuide") 39 | public Parameters uninstall( 40 | @OperationParam(name = "name", min = 1, max = 1) String name, 41 | @OperationParam(name = "version", min = 1, max = 1) String version) { 42 | 43 | packageInstallerSvc.uninstall( 44 | new PackageInstallationSpec().setName(name).setVersion(version)); 45 | return new Parameters(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ig/ImplementationGuideR5OperationProvider.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ig; 2 | 3 | import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc; 4 | import ca.uhn.fhir.jpa.packages.PackageInstallationSpec; 5 | import ca.uhn.fhir.jpa.starter.annotations.OnR5Condition; 6 | import ca.uhn.fhir.rest.annotation.Operation; 7 | import ca.uhn.fhir.rest.annotation.OperationParam; 8 | import org.hl7.fhir.r5.model.Base64BinaryType; 9 | import org.hl7.fhir.r5.model.Parameters; 10 | import org.springframework.context.annotation.Conditional; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.io.IOException; 14 | 15 | @Conditional({OnR5Condition.class, IgConfigCondition.class}) 16 | @Service 17 | public class ImplementationGuideR5OperationProvider implements IImplementationGuideOperationProvider { 18 | 19 | IPackageInstallerSvc packageInstallerSvc; 20 | 21 | public ImplementationGuideR5OperationProvider(IPackageInstallerSvc packageInstallerSvc) { 22 | this.packageInstallerSvc = packageInstallerSvc; 23 | } 24 | 25 | @Operation(name = "$install", typeName = "ImplementationGuide") 26 | public Parameters install( 27 | @OperationParam(name = "npmContent", min = 1, max = 1) Base64BinaryType implementationGuide) { 28 | try { 29 | 30 | packageInstallerSvc.install( 31 | IImplementationGuideOperationProvider.toPackageInstallationSpec(implementationGuide.getValue())); 32 | } catch (IOException e) { 33 | throw new RuntimeException(e); 34 | } 35 | return new Parameters(); 36 | } 37 | 38 | @Operation(name = "$uninstall", typeName = "ImplementationGuide") 39 | public Parameters uninstall( 40 | @OperationParam(name = "name", min = 1, max = 1) String name, 41 | @OperationParam(name = "version", min = 1, max = 1) String version) { 42 | 43 | packageInstallerSvc.uninstall( 44 | new PackageInstallationSpec().setName(name).setVersion(version)); 45 | return new Parameters(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ips/IpsConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ips; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class IpsConfigCondition implements Condition { 8 | 9 | @Override 10 | public boolean matches(ConditionContext theConditionContext, AnnotatedTypeMetadata theAnnotatedTypeMetadata) { 11 | String property = theConditionContext.getEnvironment().getProperty("hapi.fhir.ips_enabled"); 12 | return Boolean.parseBoolean(property); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/ips/StarterIpsConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.ips; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.ips.api.IIpsGenerationStrategy; 5 | import ca.uhn.fhir.jpa.ips.generator.IIpsGeneratorSvc; 6 | import ca.uhn.fhir.jpa.ips.generator.IpsGeneratorSvcImpl; 7 | import ca.uhn.fhir.jpa.ips.jpa.DefaultJpaIpsGenerationStrategy; 8 | import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Conditional; 11 | 12 | @Conditional(IpsConfigCondition.class) 13 | public class StarterIpsConfig { 14 | @Bean 15 | IIpsGenerationStrategy ipsGenerationStrategy() { 16 | return new DefaultJpaIpsGenerationStrategy(); 17 | } 18 | 19 | @Bean 20 | public IpsOperationProvider ipsOperationProvider(IIpsGeneratorSvc theIpsGeneratorSvc) { 21 | return new IpsOperationProvider(theIpsGeneratorSvc); 22 | } 23 | 24 | @Bean 25 | public IIpsGeneratorSvc ipsGeneratorSvcImpl( 26 | FhirContext theFhirContext, IIpsGenerationStrategy theGenerationStrategy) { 27 | return new IpsGeneratorSvcImpl(theFhirContext, theGenerationStrategy); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/mdm/MdmConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.mdm; 2 | 3 | import ca.uhn.fhir.jpa.mdm.config.MdmConsumerConfig; 4 | import ca.uhn.fhir.jpa.mdm.config.MdmSubmitterConfig; 5 | import ca.uhn.fhir.jpa.searchparam.config.NicknameServiceConfig; 6 | import ca.uhn.fhir.jpa.starter.AppProperties; 7 | import ca.uhn.fhir.mdm.api.IMdmSettings; 8 | import ca.uhn.fhir.mdm.rules.config.MdmRuleValidator; 9 | import ca.uhn.fhir.mdm.rules.config.MdmSettings; 10 | import org.apache.commons.io.IOUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Conditional; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.core.io.DefaultResourceLoader; 17 | import org.springframework.core.io.Resource; 18 | 19 | import java.io.IOException; 20 | import java.nio.charset.StandardCharsets; 21 | 22 | @Configuration 23 | @Conditional(MdmConfigCondition.class) 24 | @Import({MdmConsumerConfig.class, MdmSubmitterConfig.class, NicknameServiceConfig.class}) 25 | public class MdmConfig { 26 | 27 | @Bean 28 | IMdmSettings mdmSettings(@Autowired MdmRuleValidator theMdmRuleValidator, AppProperties appProperties) 29 | throws IOException { 30 | DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); 31 | Resource resource = resourceLoader.getResource(appProperties.getMdm_rules_json_location()); 32 | String json = IOUtils.toString(resource.getInputStream(), StandardCharsets.UTF_8); 33 | return new MdmSettings(theMdmRuleValidator) 34 | .setEnabled(appProperties.getMdm_enabled()) 35 | .setScriptText(json); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/mdm/MdmConfigCondition.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.mdm; 2 | 3 | import org.springframework.context.annotation.Condition; 4 | import org.springframework.context.annotation.ConditionContext; 5 | import org.springframework.core.type.AnnotatedTypeMetadata; 6 | 7 | public class MdmConfigCondition implements Condition { 8 | @Override 9 | public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) { 10 | String property = conditionContext.getEnvironment().getProperty("hapi.fhir.mdm_enabled"); 11 | return Boolean.parseBoolean(property); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/terminology/TerminologyConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.terminology; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.context.support.ConceptValidationOptions; 5 | import ca.uhn.fhir.context.support.IValidationSupport; 6 | import ca.uhn.fhir.context.support.ValidationSupportContext; 7 | import ca.uhn.fhir.jpa.starter.AppProperties; 8 | import ca.uhn.fhir.jpa.starter.common.StarterJpaConfig; 9 | import ca.uhn.fhir.jpa.starter.common.validation.OnRemoteTerminologyPresent; 10 | import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; 11 | import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Conditional; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.annotation.Import; 16 | 17 | @Configuration 18 | @Conditional(OnRemoteTerminologyPresent.class) 19 | @Import(StarterJpaConfig.class) 20 | public class TerminologyConfig { 21 | 22 | @Bean(name = "myHybridRemoteValidationSupportChain") 23 | public IValidationSupport addRemoteValidation( 24 | ValidationSupportChain theValidationSupport, FhirContext theFhirContext, AppProperties theAppProperties) { 25 | var values = theAppProperties.getRemoteTerminologyServicesMap().values(); 26 | 27 | // If the remote terminology service is "*" and is the only one then forward all requests to the remote 28 | // terminology service 29 | if (values.size() == 1 && "*".equalsIgnoreCase(values.iterator().next().getSystem())) { 30 | var remoteSystem = values.iterator().next(); 31 | theValidationSupport.addValidationSupport( 32 | 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl())); 33 | return theValidationSupport; 34 | 35 | // If there are multiple remote terminology services, then add each one to the validation chain 36 | } else { 37 | values.forEach((remoteSystem) -> theValidationSupport.addValidationSupport( 38 | 0, new RemoteTerminologyServiceValidationSupport(theFhirContext, remoteSystem.getUrl()) { 39 | @Override 40 | public boolean isCodeSystemSupported( 41 | ValidationSupportContext theValidationSupportContext, String theSystem) { 42 | return remoteSystem.getSystem().equalsIgnoreCase(theSystem); 43 | } 44 | 45 | @Override 46 | public CodeValidationResult validateCode( 47 | ValidationSupportContext theValidationSupportContext, 48 | ConceptValidationOptions theOptions, 49 | String theCodeSystem, 50 | String theCode, 51 | String theDisplay, 52 | String theValueSetUrl) { 53 | if (remoteSystem.getSystem().equalsIgnoreCase(theCodeSystem)) { 54 | return super.validateCode( 55 | theValidationSupportContext, 56 | theOptions, 57 | theCodeSystem, 58 | theCode, 59 | theDisplay, 60 | theValueSetUrl); 61 | } 62 | return null; 63 | } 64 | })); 65 | } 66 | return theValidationSupport; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/util/JpaHibernatePropertiesProvider.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.util; 2 | 3 | import ca.uhn.fhir.context.ConfigurationException; 4 | import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; 5 | import org.hibernate.dialect.Dialect; 6 | import org.hibernate.engine.jdbc.dialect.internal.StandardDialectResolver; 7 | import org.hibernate.engine.jdbc.dialect.spi.DatabaseMetaDataDialectResolutionInfoAdapter; 8 | import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; 9 | 10 | import java.sql.Connection; 11 | import java.sql.SQLException; 12 | import javax.sql.DataSource; 13 | 14 | public class JpaHibernatePropertiesProvider extends HibernatePropertiesProvider { 15 | 16 | private final Dialect dialect; 17 | 18 | public JpaHibernatePropertiesProvider(LocalContainerEntityManagerFactoryBean myEntityManagerFactory) { 19 | DataSource connection = myEntityManagerFactory.getDataSource(); 20 | try (Connection dbConnection = connection.getConnection()) { 21 | dialect = new StandardDialectResolver() 22 | .resolveDialect(new DatabaseMetaDataDialectResolutionInfoAdapter(dbConnection.getMetaData())); 23 | } catch (SQLException sqlException) { 24 | throw new ConfigurationException(sqlException.getMessage(), sqlException); 25 | } 26 | } 27 | 28 | @Override 29 | public Dialect getDialect() { 30 | return dialect; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/web/CustomContentFilesConfigurer.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.web; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.io.FileUrlResource; 8 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 10 | 11 | import java.net.MalformedURLException; 12 | 13 | @Configuration 14 | @ConditionalOnProperty(prefix = "hapi.fhir", name = "custom_content_path") 15 | public class CustomContentFilesConfigurer implements WebMvcConfigurer { 16 | 17 | public static final String CUSTOM_CONTENT = "/content"; 18 | private String customContentPath; 19 | 20 | public CustomContentFilesConfigurer(AppProperties appProperties) { 21 | customContentPath = appProperties.getCustom_content_path(); 22 | if (customContentPath.endsWith("/")) 23 | customContentPath = customContentPath.substring(0, customContentPath.lastIndexOf('/')); 24 | } 25 | 26 | @Override 27 | public void addResourceHandlers(@NotNull ResourceHandlerRegistry theRegistry) { 28 | if (!theRegistry.hasMappingForPattern(CUSTOM_CONTENT + "/**")) { 29 | 30 | try { 31 | theRegistry 32 | .addResourceHandler(CUSTOM_CONTENT + "/**") 33 | .addResourceLocations(new FileUrlResource(customContentPath)); 34 | } catch (MalformedURLException e) { 35 | throw new RuntimeException(e); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/web/JobController.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.web; 2 | 3 | import ca.uhn.fhir.batch2.api.IJobCoordinator; 4 | import ca.uhn.fhir.batch2.api.JobOperationResultJson; 5 | import ca.uhn.fhir.batch2.model.JobInstance; 6 | import ca.uhn.fhir.batch2.model.StatusEnum; 7 | import ca.uhn.fhir.batch2.models.JobInstanceFetchRequest; 8 | import jakarta.validation.constraints.Min; 9 | import org.springframework.data.domain.Sort; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | 15 | @RestController 16 | @RequestMapping("control") 17 | public class JobController { 18 | private final IJobCoordinator theJobCoordinator; 19 | 20 | public JobController(IJobCoordinator theJobCoordinator) { 21 | this.theJobCoordinator = theJobCoordinator; 22 | } 23 | 24 | @RequestMapping(value = JobController.JOBS, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) 25 | public List getAllJobs( 26 | @RequestParam(name = "pageStart") @Min(0) int pageStart, 27 | @RequestParam(name = "batchSize") int batchSize, 28 | @RequestParam(name = "jobStatus", required = false) StatusEnum jobStatus) { 29 | JobInstanceFetchRequest jobInstanceFetchRequest = new JobInstanceFetchRequest(); 30 | jobInstanceFetchRequest.setPageStart(pageStart); 31 | jobInstanceFetchRequest.setBatchSize(batchSize); 32 | jobInstanceFetchRequest.setJobStatus(jobStatus != null ? jobStatus.toString() : ""); 33 | jobInstanceFetchRequest.setSort(Sort.by(Sort.Direction.DESC, JobController.MY_CREATE_TIME)); 34 | 35 | return theJobCoordinator.fetchAllJobInstances(jobInstanceFetchRequest).getContent(); 36 | } 37 | 38 | @RequestMapping( 39 | value = JobController.JOBS, 40 | method = RequestMethod.DELETE, 41 | produces = MediaType.APPLICATION_JSON_VALUE) 42 | public JobOperationResultJson cancelInstance(@RequestParam(name = "instanceId") String instanceId) { 43 | return theJobCoordinator.cancelInstance(instanceId); 44 | } 45 | 46 | public static final String JOBS = "jobs"; 47 | public static final String MY_CREATE_TIME = "myCreateTime"; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/ca/uhn/fhir/jpa/starter/web/WebAppFilesConfigurer.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter.web; 2 | 3 | import ca.uhn.fhir.jpa.starter.AppProperties; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.core.Ordered; 8 | import org.springframework.core.io.FileUrlResource; 9 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 10 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 11 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 12 | 13 | import java.net.MalformedURLException; 14 | import java.net.URI; 15 | 16 | @Configuration 17 | @ConditionalOnProperty(prefix = "hapi.fhir", name = "app_content_path") 18 | public class WebAppFilesConfigurer implements WebMvcConfigurer { 19 | 20 | public static final String WEB_CONTENT = "web"; 21 | private String appContentPath; 22 | 23 | public WebAppFilesConfigurer(AppProperties appProperties) { 24 | appContentPath = appProperties.getApp_content_path(); 25 | if (appContentPath.endsWith("/")) appContentPath = appContentPath.substring(0, appContentPath.lastIndexOf('/')); 26 | } 27 | 28 | @Override 29 | public void addResourceHandlers(@NotNull ResourceHandlerRegistry theRegistry) { 30 | if (!theRegistry.hasMappingForPattern(WEB_CONTENT + "/**")) { 31 | { 32 | try { 33 | theRegistry 34 | .addResourceHandler(WEB_CONTENT + "/**") 35 | .addResourceLocations(new FileUrlResource(appContentPath)); 36 | } catch (MalformedURLException e) { 37 | throw new RuntimeException(e); 38 | } 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | public void addViewControllers(@NotNull ViewControllerRegistry registry) { 45 | String path = URI.create(appContentPath).getPath(); 46 | String lastSegment = path.substring(path.lastIndexOf('/') + 1); 47 | 48 | registry.addViewController(WEB_CONTENT + "/" + lastSegment) 49 | .setViewName("redirect:" + lastSegment + "/index.html"); 50 | registry.addViewController(WEB_CONTENT + "/" + lastSegment + "/").setViewName("redirect:index.html"); 51 | 52 | registry.setOrder(Ordered.HIGHEST_PRECEDENCE); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/mdm-rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "mdmTypes": ["Patient", "Practitioner"], 4 | "candidateSearchParams": [ 5 | { 6 | "resourceType": "Patient", 7 | "searchParams": ["birthdate"] 8 | }, 9 | { 10 | "resourceType": "*", 11 | "searchParams": ["identifier"] 12 | }, 13 | { 14 | "resourceType": "Patient", 15 | "searchParams": ["general-practitioner"] 16 | } 17 | ], 18 | "candidateFilterSearchParams": [ 19 | { 20 | "resourceType": "*", 21 | "searchParam": "active", 22 | "fixedValue": "true" 23 | } 24 | ], 25 | "matchFields": [ 26 | { 27 | "name": "cosine-given-name", 28 | "resourceType": "*", 29 | "resourcePath": "name.given", 30 | "matcher": { 31 | "algorithm": "COLOGNE" 32 | } 33 | }, 34 | { 35 | "name": "jaro-last-name", 36 | "resourceType": "*", 37 | "resourcePath": "name.family", 38 | "matcher": { 39 | "algorithm": "SOUNDEX" 40 | } 41 | } 42 | ], 43 | "matchResultMap": { 44 | "cosine-given-name" : "POSSIBLE_MATCH", 45 | "cosine-given-name,jaro-last-name" : "MATCH" 46 | }, 47 | "eidSystem": "http://company.io/fhir/NamingSystem/custom-eid-system" 48 | } 49 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | About This Server 5 | 6 | 7 | 8 |
9 |
10 | 11 |
12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 |

About This Server

22 |
23 |
24 |
25 |

26 | This server provides a complete implementation of the FHIR Specification 27 | using a 100% open source software stack. 28 |

29 |

30 | This server is built 31 | from a number of modules of the 32 | HAPI FHIR 33 | project, which is a 100% open-source (Apache 2.0 Licensed) Java based 34 | implementation of the FHIR specification. 35 |

36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 |

Data On This Server

45 |
46 |
47 |

48 | This UI can be customized! 49 | You might want to put rules on who can use this server here, or 50 | notices about privacy, or whatever else you want.. 51 |

52 |
53 |
54 | 55 |
56 |
57 |
58 | 59 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/tmpl-banner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | 34 | 35 |
36 | Warning! 37 |

38 |
39 | 40 |
41 | 42 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/tmpl-footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 6 | -------------------------------------------------------------------------------- /src/main/webapp/WEB-INF/templates/tmpl-home-welcome.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 41 |
42 |
43 |

44 | This server provides a complete implementation of the FHIR Specification 45 | using a 100% open source software stack. 46 |

47 |

48 | This server is built 49 | from a number of modules of the 50 | HAPI FHIR 51 | project, which is a 100% open-source (Apache 2.0 Licensed) Java based 52 | implementation of the FHIR specification. 53 |

54 |
55 |
56 |
57 | 58 | -------------------------------------------------------------------------------- /src/main/webapp/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hapifhir/hapi-fhir-jpaserver-starter/7cd0637c88b0f8d8d8679d0dbf31bfa27e6c7bf2/src/main/webapp/img/favicon.ico -------------------------------------------------------------------------------- /src/main/webapp/img/sample-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hapifhir/hapi-fhir-jpaserver-starter/7cd0637c88b0f8d8d8679d0dbf31bfa27e6c7bf2/src/main/webapp/img/sample-logo.jpg -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/CustomBeanTest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import org.junit.jupiter.api.Assertions; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | 8 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { 9 | "hapi.fhir.custom-bean-packages=some.custom.pkg1,some.custom.pkg2", 10 | "spring.datasource.url=jdbc:h2:mem:dbr4", 11 | "hapi.fhir.enable_repository_validating_interceptor=true", 12 | "hapi.fhir.fhir_version=r4", 13 | "hapi.fhir.mdm_enabled=false", 14 | "hapi.fhir.cr_enabled=false", 15 | "hapi.fhir.subscription.websocket_enabled=false", 16 | "spring.main.allow-bean-definition-overriding=true" 17 | }) 18 | class CustomBeanTest { 19 | 20 | @Autowired 21 | some.custom.pkg1.CustomBean customBean1; 22 | 23 | @Test 24 | void testCustomBeanExists() { 25 | Assertions.assertNotNull(customBean1); 26 | Assertions.assertEquals("I am alive", customBean1.getInitFlag()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/CustomInterceptorTest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import org.hl7.fhir.r4.model.Patient; 4 | import org.junit.jupiter.api.Assertions; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.web.server.LocalServerPort; 10 | 11 | import ca.uhn.fhir.context.FhirContext; 12 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 13 | import ca.uhn.fhir.rest.client.api.IGenericClient; 14 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 15 | 16 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { 17 | "hapi.fhir.custom-bean-packages=some.custom.pkg1", 18 | "hapi.fhir.custom-interceptor-classes=some.custom.pkg1.CustomInterceptorBean,some.custom.pkg1.CustomInterceptorPojo", 19 | "spring.datasource.url=jdbc:h2:mem:dbr4", 20 | "hapi.fhir.cr_enabled=false", 21 | // "hapi.fhir.enable_repository_validating_interceptor=true", 22 | "hapi.fhir.fhir_version=r4" 23 | }) 24 | 25 | class CustomInterceptorTest { 26 | 27 | @LocalServerPort 28 | private int port; 29 | 30 | @Autowired 31 | private IFhirResourceDao patientResourceDao; 32 | 33 | private IGenericClient client; 34 | private FhirContext ctx; 35 | 36 | @BeforeEach 37 | void setUp() { 38 | ctx = FhirContext.forR4(); 39 | ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 40 | ctx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 41 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 42 | client = ctx.newRestfulGenericClient(ourServerBase); 43 | 44 | // Properties props = new Properties(); 45 | // props.put("spring.autoconfigure.exclude", "org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"); 46 | } 47 | 48 | @Test 49 | void testAuditInterceptors() { 50 | 51 | // we registered two custom interceptors via the property 'hapi.fhir.custom-interceptor-classes' 52 | // one is discovered as a Spring Bean, one instantiated via reflection 53 | // both should be registered with the server and will add a custom extension to any Patient resource created 54 | // so we can verify they were registered 55 | 56 | Patient pat = new Patient(); 57 | String patId = client.create().resource(pat).execute().getId().getIdPart(); 58 | 59 | Patient readPat = client.read().resource(Patient.class).withId(patId).execute(); 60 | 61 | Assertions.assertNotNull(readPat.getExtensionByUrl("http://some.custom.pkg1/CustomInterceptorBean")); 62 | Assertions.assertNotNull(readPat.getExtensionByUrl("http://some.custom.pkg1/CustomInterceptorPojo")); 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/CustomOperationTest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.rest.api.MethodOutcome; 5 | import ca.uhn.fhir.rest.client.api.IGenericClient; 6 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 7 | import org.hl7.fhir.r4.model.Binary; 8 | import org.hl7.fhir.r4.model.Parameters; 9 | import org.junit.jupiter.api.Assertions; 10 | import org.junit.jupiter.api.BeforeEach; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.web.server.LocalServerPort; 14 | 15 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { 16 | "hapi.fhir.custom-bean-packages=some.custom.pkg1", 17 | "hapi.fhir.custom-provider-classes=some.custom.pkg1.CustomOperationBean,some.custom.pkg1.CustomOperationPojo", 18 | "spring.datasource.url=jdbc:h2:mem:dbr4", 19 | "hapi.fhir.cr_enabled=false", 20 | // "hapi.fhir.enable_repository_validating_interceptor=true", 21 | "hapi.fhir.fhir_version=r4" 22 | }) 23 | 24 | class CustomOperationTest { 25 | 26 | @LocalServerPort 27 | private int port; 28 | 29 | private IGenericClient client; 30 | private FhirContext ctx; 31 | 32 | @BeforeEach 33 | void setUp() { 34 | ctx = FhirContext.forR4(); 35 | ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 36 | ctx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 37 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 38 | client = ctx.newRestfulGenericClient(ourServerBase); 39 | 40 | // Properties props = new Properties(); 41 | // props.put("spring.autoconfigure.exclude", "org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration"); 42 | } 43 | 44 | @Test 45 | void testCustomOperations() { 46 | 47 | // we registered two custom operations via the property 'hapi.fhir.custom-provider-classes' 48 | // one is discovered as a Spring Bean ($springBeanOperation), one instantiated via reflection ($pojoOperation) 49 | // both should be registered with the server and will add a custom operation. 50 | 51 | // test Spring bean operation 52 | MethodOutcome springBeanOutcome = client.operation().onServer().named("$springBeanOperation") 53 | .withNoParameters(Parameters.class).returnMethodOutcome().execute(); 54 | 55 | // the hapi client will return our operation result (just a string) as a Binary with the string stored as the 56 | // data 57 | Assertions.assertEquals(200, springBeanOutcome.getResponseStatusCode()); 58 | Binary springReturnResource = (Binary) springBeanOutcome.getResource(); 59 | String springReturn = new String(springReturnResource.getData()); 60 | Assertions.assertEquals("springBean", springReturn); 61 | 62 | // test Pojo bean 63 | MethodOutcome pojoOutcome = client.operation().onServer().named("$pojoOperation") 64 | .withNoParameters(Parameters.class).returnMethodOutcome().execute(); 65 | 66 | Assertions.assertEquals(200, pojoOutcome.getResponseStatusCode()); 67 | Binary pojoReturnResource = (Binary) pojoOutcome.getResource(); 68 | String pojoReturn = new String(pojoReturnResource.getData()); 69 | Assertions.assertEquals("pojo", pojoReturn); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDbpmR5IT.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.migrate.DriverTypeEnum; 5 | import ca.uhn.fhir.jpa.migrate.JdbcUtils; 6 | import ca.uhn.fhir.rest.client.api.IGenericClient; 7 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 8 | import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; 9 | import org.hl7.fhir.instance.model.api.IIdType; 10 | import org.hl7.fhir.r5.model.Patient; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.boot.test.web.server.LocalServerPort; 17 | import org.springframework.test.context.junit.jupiter.SpringExtension; 18 | import org.springframework.transaction.PlatformTransactionManager; 19 | import org.springframework.transaction.support.TransactionTemplate; 20 | 21 | import javax.sql.DataSource; 22 | import java.sql.SQLException; 23 | import java.util.Set; 24 | 25 | import static org.assertj.core.api.Assertions.assertThat; 26 | import static org.junit.jupiter.api.Assertions.assertTrue; 27 | 28 | @ExtendWith(SpringExtension.class) 29 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = 30 | { 31 | "spring.datasource.url=jdbc:h2:mem:dbr5_dbpm", 32 | "hapi.fhir.fhir_version=r5", 33 | "hapi.fhir.partitioning.database_partition_mode_enabled=true", 34 | "hapi.fhir.partitioning.patient_id_partitioning_mode=true" 35 | }) 36 | public class ExampleServerDbpmR5IT { 37 | 38 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); 39 | private IGenericClient ourClient; 40 | private FhirContext ourCtx; 41 | 42 | 43 | @LocalServerPort 44 | private int port; 45 | 46 | @Autowired 47 | private DataSource myDataSource; 48 | 49 | @Autowired 50 | private PlatformTransactionManager myTxManager; 51 | 52 | 53 | @Test 54 | void testCreateAndRead() { 55 | Patient pt = new Patient(); 56 | pt.setId("A"); 57 | pt.setActive(true); 58 | IIdType id = ourClient.update().resource(pt).execute().getId(); 59 | 60 | Patient pt2 = ourClient.read().resource(Patient.class).withId("Patient/A").execute(); 61 | assertTrue(pt2.getActive()); 62 | } 63 | 64 | 65 | @Test 66 | public void testValidateSchema() throws SQLException { 67 | TransactionTemplate tt = new TransactionTemplate(myTxManager); 68 | DriverTypeEnum.ConnectionProperties cp = new DriverTypeEnum.ConnectionProperties(myDataSource, tt, DriverTypeEnum.H2_EMBEDDED); 69 | Set columns = JdbcUtils.getPrimaryKeyColumns(cp, "HFJ_RESOURCE"); 70 | assertThat(columns).containsExactlyInAnyOrder("RES_ID", "PARTITION_ID"); 71 | } 72 | 73 | 74 | @BeforeEach 75 | void beforeEach() { 76 | 77 | ourCtx = FhirContext.forR5(); 78 | ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 79 | ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 80 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 81 | ourClient = ourCtx.newRestfulGenericClient(ourServerBase); 82 | ourClient.registerInterceptor(new LoggingInterceptor(true)); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerDstu2IT.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.model.dstu2.resource.Patient; 5 | import ca.uhn.fhir.rest.client.api.IGenericClient; 6 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 7 | import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; 8 | import org.hl7.fhir.instance.model.api.IIdType; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.ExtendWith; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.web.server.LocalServerPort; 14 | import org.springframework.test.context.junit.jupiter.SpringExtension; 15 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; 17 | 18 | @ExtendWith(SpringExtension.class) 19 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = 20 | { 21 | "hapi.fhir.fhir_version=dstu2", 22 | "spring.datasource.url=jdbc:h2:mem:dbr2", 23 | "hapi.fhir.cr_enabled=false", 24 | }) 25 | class ExampleServerDstu2IT { 26 | 27 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); 28 | private IGenericClient ourClient; 29 | private FhirContext ourCtx; 30 | 31 | @LocalServerPort 32 | private int port; 33 | 34 | @Test 35 | void testCreateAndRead() { 36 | 37 | String methodName = "testCreateResourceConditional"; 38 | 39 | Patient pt = new Patient(); 40 | pt.addName().addFamily(methodName); 41 | IIdType id = ourClient.create().resource(pt).execute().getId(); 42 | Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); 43 | assertEquals(methodName, pt2.getName().get(0).getFamily().get(0).getValue()); 44 | } 45 | 46 | 47 | @BeforeEach 48 | void beforeEach() { 49 | 50 | ourCtx = FhirContext.forDstu2(); 51 | ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 52 | ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 53 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 54 | ourClient = ourCtx.newRestfulGenericClient(ourServerBase); 55 | ourClient.registerInterceptor(new LoggingInterceptor(true)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/ExampleServerR4BIT.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.rest.client.api.IGenericClient; 5 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 6 | import org.hl7.fhir.instance.model.api.IIdType; 7 | import org.hl7.fhir.r4b.model.Bundle; 8 | import org.hl7.fhir.r4b.model.Patient; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Order; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.web.server.LocalServerPort; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | 17 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, 18 | classes = {Application.class}, 19 | properties = { 20 | "spring.datasource.url=jdbc:h2:mem:dbr4b", 21 | "hapi.fhir.enable_repository_validating_interceptor=true", 22 | "hapi.fhir.fhir_version=r4b", 23 | "hapi.fhir.subscription.websocket_enabled=false", 24 | "hapi.fhir.mdm_enabled=false", 25 | "hapi.fhir.cr_enabled=false", 26 | // Override is currently required when using MDM as the construction of the MDM 27 | // beans are ambiguous as they are constructed multiple places. This is evident 28 | // when running in a spring boot environment 29 | "spring.main.allow-bean-definition-overriding=true"}) 30 | class ExampleServerR4BIT { 31 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerR4BIT.class); 32 | private IGenericClient ourClient; 33 | private FhirContext ourCtx; 34 | 35 | @LocalServerPort 36 | private int port; 37 | 38 | @Test 39 | @Order(0) 40 | void testCreateAndRead() { 41 | String methodName = "testCreateAndRead"; 42 | ourLog.info("Entering " + methodName + "()..."); 43 | 44 | Patient pt = new Patient(); 45 | pt.setActive(true); 46 | pt.getBirthDateElement().setValueAsString("2020-01-01"); 47 | pt.addIdentifier().setSystem("http://foo").setValue("12345"); 48 | pt.addName().setFamily(methodName); 49 | IIdType id = ourClient.create().resource(pt).execute().getId(); 50 | 51 | Patient pt2 = ourClient.read().resource(Patient.class).withId(id).execute(); 52 | assertEquals(methodName, pt2.getName().get(0).getFamily()); 53 | 54 | } 55 | 56 | 57 | @Test 58 | void testBatchPutWithIdenticalTags() { 59 | String batchPuts = "{\n" + 60 | "\t\"resourceType\": \"Bundle\",\n" + 61 | "\t\"id\": \"patients\",\n" + 62 | "\t\"type\": \"batch\",\n" + 63 | "\t\"entry\": [\n" + 64 | "\t\t{\n" + 65 | "\t\t\t\"request\": {\n" + 66 | "\t\t\t\t\"method\": \"PUT\",\n" + 67 | "\t\t\t\t\"url\": \"Patient/pat-1\"\n" + 68 | "\t\t\t},\n" + 69 | "\t\t\t\"resource\": {\n" + 70 | "\t\t\t\t\"resourceType\": \"Patient\",\n" + 71 | "\t\t\t\t\"id\": \"pat-1\",\n" + 72 | "\t\t\t\t\"meta\": {\n" + 73 | "\t\t\t\t\t\"tag\": [\n" + 74 | "\t\t\t\t\t\t{\n" + 75 | "\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + 76 | "\t\t\t\t\t\t\t\"code\": \"value2\"\n" + 77 | "\t\t\t\t\t\t}\n" + 78 | "\t\t\t\t\t]\n" + 79 | "\t\t\t\t}\n" + 80 | "\t\t\t},\n" + 81 | "\t\t\t\"fullUrl\": \"/Patient/pat-1\"\n" + 82 | "\t\t},\n" + 83 | "\t\t{\n" + 84 | "\t\t\t\"request\": {\n" + 85 | "\t\t\t\t\"method\": \"PUT\",\n" + 86 | "\t\t\t\t\"url\": \"Patient/pat-2\"\n" + 87 | "\t\t\t},\n" + 88 | "\t\t\t\"resource\": {\n" + 89 | "\t\t\t\t\"resourceType\": \"Patient\",\n" + 90 | "\t\t\t\t\"id\": \"pat-2\",\n" + 91 | "\t\t\t\t\"meta\": {\n" + 92 | "\t\t\t\t\t\"tag\": [\n" + 93 | "\t\t\t\t\t\t{\n" + 94 | "\t\t\t\t\t\t\t\"system\": \"http://mysystem.org\",\n" + 95 | "\t\t\t\t\t\t\t\"code\": \"value2\"\n" + 96 | "\t\t\t\t\t\t}\n" + 97 | "\t\t\t\t\t]\n" + 98 | "\t\t\t\t}\n" + 99 | "\t\t\t},\n" + 100 | "\t\t\t\"fullUrl\": \"/Patient/pat-2\"\n" + 101 | "\t\t}\n" + 102 | "\t]\n" + 103 | "}"; 104 | Bundle bundle = FhirContext.forR4B().newJsonParser().parseResource(Bundle.class, batchPuts); 105 | ourClient.transaction().withBundle(bundle).execute(); 106 | } 107 | 108 | 109 | 110 | @BeforeEach 111 | void beforeEach() { 112 | 113 | ourCtx = FhirContext.forR4B(); 114 | ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 115 | ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 116 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 117 | ourClient = ourCtx.newRestfulGenericClient(ourServerBase); 118 | 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/IServerSupport.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.jpa.api.dao.DaoRegistry; 5 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 6 | import ca.uhn.fhir.rest.client.api.IGenericClient; 7 | import com.google.common.base.Charsets; 8 | import org.apache.commons.io.IOUtils; 9 | import org.hl7.fhir.instance.model.api.IBaseBundle; 10 | import org.hl7.fhir.instance.model.api.IBaseResource; 11 | import org.springframework.core.io.DefaultResourceLoader; 12 | import org.springframework.core.io.Resource; 13 | 14 | import java.io.File; 15 | import java.io.FileInputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | 19 | public interface IServerSupport { 20 | 21 | default IBaseResource loadResource(String theLocation, FhirContext theFhirContext, DaoRegistry theDaoRegistry) throws IOException { 22 | String json = stringFromResource(theLocation); 23 | IBaseResource resource = theFhirContext.newJsonParser().parseResource(json); 24 | IFhirResourceDao dao = theDaoRegistry.getResourceDao(resource.getIdElement().getResourceType()); 25 | if (dao == null) { 26 | return null; 27 | } else { 28 | dao.update(resource); 29 | return resource; 30 | } 31 | } 32 | 33 | default IBaseBundle loadBundle(String theLocation, FhirContext theFhirContext, IGenericClient theClient) throws IOException { 34 | String json = stringFromResource(theLocation); 35 | IBaseBundle bundle = (IBaseBundle) theFhirContext.newJsonParser().parseResource(json); 36 | return theClient.transaction().withBundle(bundle).execute(); 37 | } 38 | 39 | default String stringFromResource(String theLocation) throws IOException { 40 | InputStream is = null; 41 | if (theLocation.startsWith(File.separator)) { 42 | is = new FileInputStream(theLocation); 43 | } else { 44 | DefaultResourceLoader resourceLoader = new DefaultResourceLoader(); 45 | Resource resource = resourceLoader.getResource(theLocation); 46 | is = resource.getInputStream(); 47 | } 48 | return IOUtils.toString(is, Charsets.UTF_8); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/MdmTest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import ca.uhn.fhir.jpa.model.config.SubscriptionSettings; 6 | import org.hl7.fhir.dstu2.model.Subscription.SubscriptionChannelType; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; 12 | import ca.uhn.fhir.jpa.nickname.INicknameSvc; 13 | 14 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { 15 | "hapi.fhir.fhir_version=r4", 16 | "hapi.fhir.mdm_enabled=true" 17 | }) 18 | class MdmTest { 19 | @Autowired 20 | INicknameSvc nicknameService; 21 | 22 | @Autowired 23 | JpaStorageSettings jpaStorageSettings; 24 | 25 | @Autowired 26 | SubscriptionSettings subscriptionSettings; 27 | 28 | @Test 29 | void testApplicationStartedSuccessfully() { 30 | assertThat(nicknameService).isNotNull(); 31 | assertThat(subscriptionSettings.getSupportedSubscriptionTypes()).contains(SubscriptionChannelType.MESSAGE); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/MeasureEvaluationConfig.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import org.opencds.cqf.fhir.cql.EvaluationSettings; 4 | import org.opencds.cqf.fhir.cr.measure.MeasureEvaluationOptions; 5 | import org.opencds.cqf.fhir.utility.ValidationProfile; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import java.util.Map; 10 | 11 | @Configuration 12 | public class MeasureEvaluationConfig { 13 | 14 | @Bean 15 | public MeasureEvaluationOptions measureEvaluationOptions(){ 16 | return new MeasureEvaluationOptions(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/MultitenantServerR4IT.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import ca.uhn.fhir.context.FhirContext; 4 | import ca.uhn.fhir.rest.api.CacheControlDirective; 5 | import ca.uhn.fhir.rest.client.api.IGenericClient; 6 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 7 | import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; 8 | import ca.uhn.fhir.rest.client.interceptor.UrlTenantSelectionInterceptor; 9 | import ca.uhn.fhir.rest.server.provider.ProviderConstants; 10 | import org.hl7.fhir.r4.model.Bundle; 11 | import org.hl7.fhir.r4.model.CodeType; 12 | import org.hl7.fhir.r4.model.IntegerType; 13 | import org.hl7.fhir.r4.model.Parameters; 14 | import org.hl7.fhir.r4.model.Patient; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.Test; 17 | import org.junit.jupiter.api.extension.ExtendWith; 18 | import org.springframework.boot.test.context.SpringBootTest; 19 | import org.springframework.boot.test.web.server.LocalServerPort; 20 | import org.springframework.test.context.junit.jupiter.SpringExtension; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertEquals; 23 | 24 | @ExtendWith(SpringExtension.class) 25 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = 26 | { 27 | "spring.datasource.url=jdbc:h2:mem:dbr4-mt", 28 | "hapi.fhir.fhir_version=r4", 29 | "hapi.fhir.subscription.websocket_enabled=true", 30 | "hapi.fhir.cr_enabled=false", 31 | "hapi.fhir.partitioning.partitioning_include_in_search_hashes=false", 32 | "hapi.fhir.partitioning.request_tenant_partitioning_mode=true", 33 | }) 34 | class MultitenantServerR4IT { 35 | 36 | 37 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExampleServerDstu2IT.class); 38 | private static UrlTenantSelectionInterceptor ourClientTenantInterceptor; 39 | private IGenericClient ourClient; 40 | private FhirContext ourCtx; 41 | @LocalServerPort 42 | private int port; 43 | 44 | @Test 45 | void testCreateAndReadInTenantA() { 46 | 47 | 48 | // Create tenant A 49 | ourClientTenantInterceptor.setTenantId("DEFAULT"); 50 | ourClient 51 | .operation() 52 | .onServer() 53 | .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) 54 | .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(1)) 55 | .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-A")) 56 | .execute(); 57 | 58 | 59 | ourClientTenantInterceptor.setTenantId("TENANT-A"); 60 | Patient pt = new Patient(); 61 | pt.addName().setFamily("Family A"); 62 | ourClient.create().resource(pt).execute().getId(); 63 | 64 | Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); 65 | assertEquals(1, searchResult.getEntry().size()); 66 | Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); 67 | assertEquals("Family A", pt2.getName().get(0).getFamily()); 68 | } 69 | 70 | @Test 71 | void testCreateAndReadInTenantB() { 72 | 73 | 74 | // Create tenant A 75 | ourClientTenantInterceptor.setTenantId("DEFAULT"); 76 | ourClient 77 | .operation() 78 | .onServer() 79 | .named(ProviderConstants.PARTITION_MANAGEMENT_CREATE_PARTITION) 80 | .withParameter(Parameters.class, ProviderConstants.PARTITION_MANAGEMENT_PARTITION_ID, new IntegerType(2)) 81 | .andParameter(ProviderConstants.PARTITION_MANAGEMENT_PARTITION_NAME, new CodeType("TENANT-B")) 82 | .execute(); 83 | 84 | 85 | ourClientTenantInterceptor.setTenantId("TENANT-B"); 86 | Patient pt = new Patient(); 87 | pt.addName().setFamily("Family B"); 88 | ourClient.create().resource(pt).execute().getId(); 89 | 90 | Bundle searchResult = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute(); 91 | assertEquals(1, searchResult.getEntry().size()); 92 | Patient pt2 = (Patient) searchResult.getEntry().get(0).getResource(); 93 | assertEquals("Family B", pt2.getName().get(0).getFamily()); 94 | } 95 | 96 | @BeforeEach 97 | void beforeEach() { 98 | 99 | ourClientTenantInterceptor = new UrlTenantSelectionInterceptor(); 100 | ourCtx = FhirContext.forR4(); 101 | ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 102 | ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 103 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 104 | ourClient = ourCtx.newRestfulGenericClient(ourServerBase); 105 | ourClient.registerInterceptor(new LoggingInterceptor(true)); 106 | ourClient.registerInterceptor(ourClientTenantInterceptor); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/ParallelUpdatesVersionConflictTest.java: -------------------------------------------------------------------------------- 1 | package ca.uhn.fhir.jpa.starter; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.Callable; 6 | import java.util.concurrent.ExecutionException; 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.Future; 10 | 11 | import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; 12 | import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; 13 | import org.hl7.fhir.r4.model.Patient; 14 | import org.hl7.fhir.r4.model.Bundle; 15 | import org.hl7.fhir.r4.model.Bundle.BundleType; 16 | import org.hl7.fhir.r4.model.Bundle.HTTPVerb; 17 | import org.junit.jupiter.api.Assertions; 18 | import org.junit.jupiter.api.BeforeEach; 19 | import org.junit.jupiter.api.Test; 20 | import org.springframework.beans.factory.annotation.Autowired; 21 | import org.springframework.boot.test.context.SpringBootTest; 22 | import org.springframework.boot.test.web.server.LocalServerPort; 23 | 24 | import ca.uhn.fhir.context.FhirContext; 25 | import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; 26 | import ca.uhn.fhir.rest.api.MethodOutcome; 27 | import ca.uhn.fhir.rest.client.api.IGenericClient; 28 | import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum; 29 | 30 | import static org.junit.jupiter.api.Assertions.assertThrows; 31 | 32 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {Application.class}, properties = { 33 | "spring.datasource.url=jdbc:h2:mem:dbr4", 34 | "hapi.fhir.fhir_version=r4", 35 | "hapi.fhir.userRequestRetryVersionConflictsInterceptorEnabled=true" 36 | }) 37 | 38 | /** 39 | * This class tests running parallel updates to a single resource with and without setting the 'X-Retry-On-Version-Conflict' header 40 | * to ensure we get the expected behavior of detecting conflicts 41 | */ 42 | public class ParallelUpdatesVersionConflictTest { 43 | 44 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ParallelUpdatesVersionConflictTest.class); 45 | 46 | @LocalServerPort 47 | private int port; 48 | 49 | private IGenericClient client; 50 | private FhirContext ctx; 51 | 52 | @BeforeEach 53 | void setUp() { 54 | ctx = FhirContext.forR4(); 55 | ctx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); 56 | ctx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); 57 | String ourServerBase = "http://localhost:" + port + "/fhir/"; 58 | client = ctx.newRestfulGenericClient(ourServerBase); 59 | } 60 | 61 | @Test 62 | void testParallelResourceUpdateBundle() throws Throwable { 63 | //send 10 bundles with updates to the patient in parallel, except the header to deconflict them 64 | Patient pat = new Patient(); 65 | String patId = client.create().resource(pat).execute().getId().getIdPart(); 66 | launchThreads(patId, true, "X-Retry-On-Version-Conflict"); 67 | } 68 | 69 | @Test 70 | void testParallelResourceUpdateNoBundle() throws Throwable { 71 | //send 10 resource puts to the patient in parallel, except the header to deconflict them 72 | Patient pat = new Patient(); 73 | String patId = client.create().resource(pat).execute().getId().getIdPart(); 74 | launchThreads(patId, false, "X-Retry-On-Version-Conflict"); 75 | } 76 | 77 | @Test 78 | void testParallelResourceUpdateBundleExpectConflict() { 79 | //send 10 bundles with updates to the patient in parallel, expect a ResourceVersionConflictException since we are not setting the retry header 80 | Patient pat = new Patient(); 81 | String patId = client.create().resource(pat).execute().getId().getIdPart(); 82 | ResourceVersionConflictException exception = assertThrows(ResourceVersionConflictException.class, () -> 83 | launchThreads(patId, true, "someotherheader")); 84 | } 85 | 86 | @Test 87 | void testParallelResourceUpdateNoBundleExpectConflict() { 88 | //send 10 resource puts to the patient in parallel, expect a ResourceVersionConflictException since we are not setting the retry header 89 | Patient pat = new Patient(); 90 | String patId = client.create().resource(pat).execute().getId().getIdPart(); 91 | ResourceVersionConflictException exception = assertThrows(ResourceVersionConflictException.class, () -> 92 | launchThreads(patId, false, "someotherheader")); 93 | } 94 | 95 | private void launchThreads(String patientId, boolean useBundles, String headerName) throws Throwable { 96 | int threadCnt = 10; 97 | ExecutorService execSvc = Executors.newFixedThreadPool(threadCnt); 98 | 99 | //launch a bunch of threads at the same time that update the same patient 100 | List> callables = new ArrayList<>(); 101 | for (int i = 0; i < threadCnt; i++) { 102 | final int cnt = i; 103 | Callable callable = new Callable<>() { 104 | @Override 105 | public Integer call() throws Exception { 106 | Patient pat = new Patient(); 107 | //make sure to change something so the server doesnt short circuit on a no-op 108 | pat.addName().setFamily("fam-" + cnt); 109 | pat.setId(patientId); 110 | 111 | if( useBundles) { 112 | Bundle b = new Bundle(); 113 | b.setType(BundleType.TRANSACTION); 114 | BundleEntryComponent bec = b.addEntry(); 115 | bec.setResource(pat); 116 | //bec.setFullUrl("Patient/" + patId); 117 | Bundle.BundleEntryRequestComponent req = bec.getRequest(); 118 | req.setUrl("Patient/" + patientId); 119 | req.setMethod(HTTPVerb.PUT); 120 | bec.setRequest(req); 121 | 122 | Bundle returnBundle = client.transaction().withBundle(b) 123 | .withAdditionalHeader(headerName, "retry; max-retries=10") 124 | .execute(); 125 | 126 | String statusString = returnBundle.getEntryFirstRep().getResponse().getStatus(); 127 | ourLog.trace("statusString->{}", statusString); 128 | try { 129 | return Integer.parseInt(statusString.substring(0,3)); 130 | }catch(NumberFormatException nfe) { 131 | return 500; 132 | } 133 | } 134 | else { 135 | MethodOutcome outcome = client.update().resource(pat).withId(patientId) 136 | .withAdditionalHeader(headerName, "retry; max-retries=10") 137 | .execute(); 138 | ourLog.trace("updated patient: " + outcome.getResponseStatusCode()); 139 | return outcome.getResponseStatusCode(); 140 | } 141 | } 142 | }; 143 | callables.add(callable); 144 | } 145 | 146 | List> futures = new ArrayList<>(); 147 | 148 | //launch them all at once 149 | for (Callable callable : callables) { 150 | futures.add(execSvc.submit(callable)); 151 | } 152 | 153 | //wait for calls to complete 154 | for (Future future : futures) { 155 | try { 156 | Integer httpResponseCode = future.get(); 157 | Assertions.assertEquals(200, httpResponseCode); 158 | } catch (InterruptedException | ExecutionException e) { 159 | //throw the ResourceVersionConflictException back up so we can test it 160 | throw e.getCause(); 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/test/java/ca/uhn/fhir/jpa/starter/SocketImplementation.java: -------------------------------------------------------------------------------- 1 | 2 | package ca.uhn.fhir.jpa.starter; 3 | 4 | import ca.uhn.fhir.rest.api.EncodingEnum; 5 | import jakarta.websocket.ClientEndpoint; 6 | import jakarta.websocket.OnMessage; 7 | import jakarta.websocket.OnOpen; 8 | import jakarta.websocket.Session; 9 | import org.slf4j.Logger; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @ClientEndpoint 15 | public class SocketImplementation { 16 | 17 | private static final Logger ourLog = org.slf4j.LoggerFactory.getLogger(SocketImplementation.class); 18 | private final String myCriteria; 19 | protected String myError; 20 | protected boolean myGotBound; 21 | private final List myMessages = new ArrayList<>(); 22 | protected int myPingCount; 23 | protected String mySubsId; 24 | private Session session; 25 | 26 | public SocketImplementation(String theCriteria, EncodingEnum theEncoding) { 27 | myCriteria = theCriteria; 28 | } 29 | 30 | public List getMessages() { 31 | return myMessages; 32 | } 33 | 34 | public void keepAlive() { 35 | if (this.session != null) { 36 | try { 37 | session.getBasicRemote().sendText("keep alive"); 38 | } catch (Throwable t) { 39 | ourLog.error("Failure", t); 40 | } 41 | } 42 | } 43 | 44 | /** 45 | * This method is executed when the client is connecting to the server. 46 | * In this case, we are sending a message to create the subscription dynamiclly 47 | * 48 | * @param session 49 | */ 50 | @OnOpen 51 | public void onConnect(Session session) { 52 | ourLog.info("Got connect: {}", session); 53 | this.session = session; 54 | try { 55 | String sending = "bind " + myCriteria; 56 | ourLog.info("Sending: {}", sending); 57 | session.getBasicRemote().sendText(sending); 58 | 59 | ourLog.info("Connection: DONE"); 60 | } catch (Throwable t) { 61 | t.printStackTrace(); 62 | ourLog.error("Failure", t); 63 | } 64 | } 65 | 66 | /** 67 | * This is the message handler for the client 68 | * 69 | * @param theMsg 70 | */ 71 | @OnMessage 72 | public void onMessage(String theMsg) { 73 | ourLog.info("Got msg: " + theMsg); 74 | myMessages.add(theMsg); 75 | 76 | if (theMsg.startsWith("bound ")) { 77 | myGotBound = true; 78 | mySubsId = (theMsg.substring("bound ".length())); 79 | } else if (myGotBound && theMsg.startsWith("add " + mySubsId + "\n")) { 80 | String text = theMsg.substring(("add " + mySubsId + "\n").length()); 81 | ourLog.info("text: " + text); 82 | } else if (theMsg.startsWith("ping ")) { 83 | myPingCount++; 84 | } else { 85 | myError = "Unexpected message: " + theMsg; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/test/java/some/custom/pkg1/CustomBean.java: -------------------------------------------------------------------------------- 1 | package some.custom.pkg1; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class CustomBean { 7 | 8 | private String initFlag; 9 | 10 | public CustomBean() { 11 | initFlag = "I am alive"; 12 | } 13 | 14 | public String getInitFlag() { 15 | return initFlag; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/some/custom/pkg1/CustomInterceptorBean.java: -------------------------------------------------------------------------------- 1 | package some.custom.pkg1; 2 | 3 | import org.hl7.fhir.instance.model.api.IBaseResource; 4 | import org.hl7.fhir.r4.model.Extension; 5 | import org.hl7.fhir.r4.model.Patient; 6 | import org.hl7.fhir.r4.model.StringType; 7 | import org.springframework.stereotype.Component; 8 | 9 | import ca.uhn.fhir.interceptor.api.Hook; 10 | import ca.uhn.fhir.interceptor.api.Interceptor; 11 | import ca.uhn.fhir.interceptor.api.Pointcut; 12 | import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 13 | import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 14 | 15 | @Interceptor 16 | @Component 17 | public class CustomInterceptorBean { 18 | 19 | @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED) 20 | void preHandleResource(ServletRequestDetails servletRequestDetails, RestOperationTypeEnum opType) { 21 | IBaseResource resource = servletRequestDetails.getResource(); 22 | 23 | // add an extension before saving the resource to mark it 24 | if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient) { 25 | Patient pat = (Patient) resource; 26 | Extension ext = pat.addExtension(); 27 | ext.setUrl("http://some.custom.pkg1/CustomInterceptorBean"); 28 | ext.setValue(new StringType("CustomInterceptorBean wuz here")); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/some/custom/pkg1/CustomInterceptorPojo.java: -------------------------------------------------------------------------------- 1 | package some.custom.pkg1; 2 | 3 | import org.hl7.fhir.instance.model.api.IBaseResource; 4 | import org.hl7.fhir.r4.model.Extension; 5 | import org.hl7.fhir.r4.model.Patient; 6 | import org.hl7.fhir.r4.model.StringType; 7 | import ca.uhn.fhir.interceptor.api.Hook; 8 | import ca.uhn.fhir.interceptor.api.Pointcut; 9 | import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 10 | import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 11 | 12 | public class CustomInterceptorPojo { 13 | 14 | @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED) 15 | void preHandleResource(ServletRequestDetails servletRequestDetails, RestOperationTypeEnum opType) { 16 | IBaseResource resource = servletRequestDetails.getResource(); 17 | 18 | // add an extension before saving the resource to mark it 19 | if (opType == RestOperationTypeEnum.CREATE && resource instanceof Patient) { 20 | Patient pat = (Patient) resource; 21 | Extension ext = pat.addExtension(); 22 | ext.setUrl("http://some.custom.pkg1/CustomInterceptorPojo"); 23 | ext.setValue(new StringType("CustomInterceptorPojo wuz here")); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/some/custom/pkg1/CustomOperationBean.java: -------------------------------------------------------------------------------- 1 | package some.custom.pkg1; 2 | 3 | import ca.uhn.fhir.rest.annotation.Operation; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.apache.commons.io.IOUtils; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Code taken from hapi documentation on how to implement an operation which handles its own request/response 13 | * ... 14 | */ 15 | 16 | @Component 17 | public class CustomOperationBean { 18 | 19 | private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CustomOperationBean.class); 20 | 21 | @Operation(name = "$springBeanOperation", manualResponse = true, manualRequest = true) 22 | public void springBeanOperation(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) 23 | throws IOException { 24 | String contentType = theServletRequest.getContentType(); 25 | byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream()); 26 | 27 | ourLog.info("Received call with content type {} and {} bytes", contentType, bytes.length); 28 | 29 | theServletResponse.setContentType("text/plain"); 30 | theServletResponse.getWriter().write("springBean"); 31 | theServletResponse.getWriter().close(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/some/custom/pkg1/CustomOperationPojo.java: -------------------------------------------------------------------------------- 1 | package some.custom.pkg1; 2 | 3 | import ca.uhn.fhir.rest.annotation.Operation; 4 | import jakarta.servlet.http.HttpServletRequest; 5 | import jakarta.servlet.http.HttpServletResponse; 6 | import org.apache.commons.io.IOUtils; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import java.io.IOException; 11 | 12 | public class CustomOperationPojo { 13 | 14 | private final Logger LOGGER = LoggerFactory.getLogger(CustomOperationPojo.class); 15 | 16 | @Operation(name = "$pojoOperation", manualResponse = true, manualRequest = true) 17 | public void $pojoOperation(HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) 18 | throws IOException { 19 | String contentType = theServletRequest.getContentType(); 20 | byte[] bytes = IOUtils.toByteArray(theServletRequest.getInputStream()); 21 | 22 | LOGGER.info("Received call with content type {} and {} bytes", contentType, bytes.length); 23 | 24 | theServletResponse.setContentType("text/plain"); 25 | theServletResponse.getWriter().write("pojo"); 26 | theServletResponse.getWriter().close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/r4/CareGaps/numer-EXM125-patient.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "numer-EXM125", 4 | "meta": { 5 | "profile": [ 6 | "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient" 7 | ] 8 | }, 9 | "extension": [ 10 | { 11 | "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-race", 12 | "extension": [ 13 | { 14 | "url": "ombCategory", 15 | "valueCoding": { 16 | "system": "urn:oid:2.16.840.1.113883.6.238", 17 | "code": "2028-9", 18 | "display": "Asian" 19 | } 20 | } 21 | ] 22 | }, 23 | { 24 | "url": "http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity", 25 | "extension": [ 26 | { 27 | "url": "ombCategory", 28 | "valueCoding": { 29 | "system": "urn:oid:2.16.840.1.113883.6.238", 30 | "code": "2135-2", 31 | "display": "Hispanic or Latino" 32 | } 33 | } 34 | ] 35 | } 36 | ], 37 | "identifier": [ 38 | { 39 | "use": "usual", 40 | "type": { 41 | "coding": [ 42 | { 43 | "system": "http://terminology.hl7.org/CodeSystem/v2-0203", 44 | "code": "MR", 45 | "display": "Medical Record Number" 46 | } 47 | ] 48 | }, 49 | "system": "http://hospital.smarthealthit.org", 50 | "value": "999999995" 51 | } 52 | ], 53 | "name": [ 54 | { 55 | "family": "McCarren", 56 | "given": [ 57 | "Karen" 58 | ] 59 | } 60 | ], 61 | "gender": "female", 62 | "birthDate": "1965-01-01" 63 | } 64 | -------------------------------------------------------------------------------- /src/test/smoketest/SMOKE_TEST.md: -------------------------------------------------------------------------------- 1 | # JPA Server Starter Smoke Tests 2 | 3 | --- 4 | 5 | When updating the supporting HAPI-FHIR version, or making changes to the JPA server starter code itself, it is recommended to run basic smoke tests to ensure the basic functionality of the server is maintained. The goal of these tests is 6 | to provide users a quick way to run groups of tests that correspond to various sections within the 7 | [HAPI-FHIR documentation][Link-HAPI-FHIR-docs]. 8 | 9 | ### Requirements 10 | Tests are written in IntelliJ [HTTP Client Request files][Link-HTTP-Client-Req-Intro]. Ultimate edition of IntelliJ 11 | is required to run these tests. 12 | 13 | For more details on integrated tooling and request syntax, there is [documentation available][Link-HTTP-Client-Req-Exploring] 14 | on the jetbrains website, in addition to the [API reference][Link-HTTP-Client-Req-API]. 15 | 16 | ### Formatting 17 | Each test file corresponds to a given section within the hapifhir documentation as close as possible. For 18 | example, there is a `plain_server.rest` file, which attemps to smoke test all basic functionality outlined in the section 19 | [within the docs][Link-HAPI-FHIR-docs-plain-server]. 20 | 21 | Individual tests are formatted as follows: 22 | ```javascript 23 | ### Test Title Here 24 | # 25 | REST-OPERATION ENDPOINT-URL 26 | // Verification Steps 27 | ``` 28 | 29 | To run these tests against a specific server, configure the server details within the `http-client.env.json` file. By default, we provide the following: 30 | ```json 31 | { 32 | "default": { 33 | "host": "localhost:8080", 34 | "username": "username", 35 | "password": "password" 36 | } 37 | } 38 | ``` 39 | 40 | ### Running the Tests 41 | Within IntelliJ, right click the file you wish to run tests in and select the `Run All` option from the menu. 42 | 43 | **Important:** Tests may not work individually when run. Often times, tests need to be run sequentially, as they depend 44 | on resources/references from previous tests to complete. _(An example of this would be adding a Patient, saving the id, 45 | then using that saved id to test if we can successfully PATCH that Patient resource. Without that saved id from the 46 | previous test creating that patient, the PATCH test will fail.)_ 47 | 48 | 49 | [Link-HAPI-FHIR-docs]: https://hapifhir.io/hapi-fhir/docs/ 50 | [Link-HAPI-FHIR-docs-plain-server]: https://hapifhir.io/hapi-fhir/docs/server_plain/server_types.html 51 | [Link-HTTP-Client-Req-Intro]: https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html 52 | [Link-HTTP-Client-Req-Exploring]: https://www.jetbrains.com/help/idea/exploring-http-syntax.html 53 | [Link-HTTP-Client-Req-API]: https://www.jetbrains.com/help/idea/http-response-handling-api-reference.html -------------------------------------------------------------------------------- /src/test/smoketest/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "host": "localhost:8080", 4 | "username": "username", 5 | "password": "password" 6 | } 7 | } -------------------------------------------------------------------------------- /src/test/smoketest/smoketestfiles/patient_batch_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Bundle", 3 | "id": "bundle-transaction", 4 | "meta": { 5 | "lastUpdated": "2014-08-18T01:43:30Z" 6 | }, 7 | "type": "transaction", 8 | "entry": [ 9 | { 10 | "resource": { 11 | "resourceType": "Patient", 12 | "text": { 13 | "status": "generated", 14 | "div": "
Some narrative
" 15 | }, 16 | "active": true, 17 | "name": [ 18 | { 19 | "use": "official", 20 | "family": "Iantorno", 21 | "given": [ 22 | "Mark" 23 | ] 24 | } 25 | ], 26 | "gender": "male", 27 | "birthDate": "1983-06-23" 28 | }, 29 | "request": { 30 | "method": "POST", 31 | "url": "Patient" 32 | } 33 | }, 34 | { 35 | "resource": { 36 | "resourceType": "Patient", 37 | "text": { 38 | "status": "generated", 39 | "div": "
Some narrative
" 40 | }, 41 | "active": true, 42 | "name": [ 43 | { 44 | "use": "official", 45 | "family": "Iantorno", 46 | "given": [ 47 | "Alexander" 48 | ] 49 | } 50 | ], 51 | "gender": "male", 52 | "birthDate": "1993-08-16" 53 | }, 54 | "request": { 55 | "method": "POST", 56 | "url": "Patient" 57 | } 58 | }, 59 | { 60 | "resource": { 61 | "resourceType": "Patient", 62 | "text": { 63 | "status": "generated", 64 | "div": "
Some narrative
" 65 | }, 66 | "active": true, 67 | "name": [ 68 | { 69 | "use": "official", 70 | "family": "Cash", 71 | "given": [ 72 | "Johnny" 73 | ] 74 | } 75 | ], 76 | "gender": "male", 77 | "birthDate": "1932-02-26" 78 | }, 79 | "request": { 80 | "method": "POST", 81 | "url": "Patient" 82 | } 83 | } 84 | ] 85 | } -------------------------------------------------------------------------------- /src/test/smoketest/smoketestfiles/patient_create.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "example", 4 | "text": { 5 | "status": "generated", 6 | "div": "
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
NamePeter James \n Chalmers ("Jim")\n
Address534 Erewhon, Pleasantville, Vic, 3999
ContactsHome: unknown. Work: (03) 5555 6473
IdMRN: 12345 (Acme Healthcare)
\n\t\t
" 7 | }, 8 | "identifier": [ 9 | { 10 | "use": "usual", 11 | "type": { 12 | "coding": [ 13 | { 14 | "system": "http://terminology.hl7.org/CodeSystem/v2-0203", 15 | "code": "MR" 16 | } 17 | ] 18 | }, 19 | "system": "urn:oid:1.2.36.146.595.217.0.1", 20 | "value": "12345", 21 | "period": { 22 | "start": "2001-05-06" 23 | }, 24 | "assigner": { 25 | "display": "Acme Healthcare" 26 | } 27 | } 28 | ], 29 | "active": true, 30 | "name": [ 31 | { 32 | "use": "official", 33 | "family": "Chalmers", 34 | "given": [ 35 | "Peter", 36 | "James" 37 | ] 38 | }, 39 | { 40 | "use": "usual", 41 | "given": [ 42 | "Jim" 43 | ] 44 | }, 45 | { 46 | "use": "maiden", 47 | "family": "Windsor", 48 | "given": [ 49 | "Peter", 50 | "James" 51 | ], 52 | "period": { 53 | "end": "2002" 54 | } 55 | } 56 | ], 57 | "telecom": [ 58 | { 59 | "use": "home" 60 | }, 61 | { 62 | "system": "phone", 63 | "value": "(03) 5555 6473", 64 | "use": "work", 65 | "rank": 1 66 | }, 67 | { 68 | "system": "phone", 69 | "value": "(03) 3410 5613", 70 | "use": "mobile", 71 | "rank": 2 72 | }, 73 | { 74 | "system": "phone", 75 | "value": "(03) 5555 8834", 76 | "use": "old", 77 | "period": { 78 | "end": "2014" 79 | } 80 | } 81 | ], 82 | "gender": "male", 83 | "birthDate": "1974-12-25", 84 | "_birthDate": { 85 | "extension": [ 86 | { 87 | "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime", 88 | "valueDateTime": "1974-12-25T14:35:45-05:00" 89 | } 90 | ] 91 | }, 92 | "deceasedBoolean": false, 93 | "address": [ 94 | { 95 | "use": "home", 96 | "type": "both", 97 | "text": "534 Erewhon St PeasantVille, Rainbow, Vic 3999", 98 | "line": [ 99 | "534 Erewhon St" 100 | ], 101 | "city": "PleasantVille", 102 | "district": "Rainbow", 103 | "state": "Vic", 104 | "postalCode": "3999", 105 | "period": { 106 | "start": "1974-12-25" 107 | } 108 | } 109 | ], 110 | "contact": [ 111 | { 112 | "relationship": [ 113 | { 114 | "coding": [ 115 | { 116 | "system": "http://terminology.hl7.org/CodeSystem/v2-0131", 117 | "code": "N" 118 | } 119 | ] 120 | } 121 | ], 122 | "name": { 123 | "family": "du Marché", 124 | "_family": { 125 | "extension": [ 126 | { 127 | "url": "http://hl7.org/fhir/StructureDefinition/humanname-own-prefix", 128 | "valueString": "VV" 129 | } 130 | ] 131 | }, 132 | "given": [ 133 | "Bénédicte" 134 | ] 135 | }, 136 | "telecom": [ 137 | { 138 | "system": "phone", 139 | "value": "+33 (237) 998327" 140 | } 141 | ], 142 | "address": { 143 | "use": "home", 144 | "type": "both", 145 | "line": [ 146 | "534 Erewhon St" 147 | ], 148 | "city": "PleasantVille", 149 | "district": "Rainbow", 150 | "state": "Vic", 151 | "postalCode": "3999", 152 | "period": { 153 | "start": "1974-12-25" 154 | } 155 | }, 156 | "gender": "female", 157 | "period": { 158 | "start": "2012" 159 | } 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /src/test/smoketest/smoketestfiles/patient_patch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "op": "add", 4 | "path": "/active", 5 | "value": false 6 | } 7 | ] -------------------------------------------------------------------------------- /src/test/smoketest/smoketestfiles/patient_process_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "MessageHeader", 3 | "id": "{{batch_patient_id}}", 4 | "text": { 5 | "status": "generated", 6 | "div": "
\n\t\t\t

Update Person resource for Peter James CHALMERS (Jim), MRN: 12345 (Acme Healthcare)

\n\t\t
" 7 | }, 8 | "eventCoding": { 9 | "system": "http://example.org/fhir/message-events", 10 | "code": "admin-notify" 11 | }, 12 | "destination": [ 13 | { 14 | "name": "Acme Message Gateway", 15 | "target": { 16 | "reference": "Device/example" 17 | }, 18 | "endpoint": "llp:10.11.12.14:5432", 19 | "receiver": { 20 | "reference": "http://acme.com/ehr/fhir/Practitioner/2323-33-4" 21 | } 22 | } 23 | ], 24 | "sender": { 25 | "reference": "Organization/1" 26 | }, 27 | "enterer": { 28 | "reference": "Practitioner/example" 29 | }, 30 | "author": { 31 | "reference": "Practitioner/example" 32 | }, 33 | "source": { 34 | "name": "Acme Central Patient Registry", 35 | "software": "FooBar Patient Manager", 36 | "version": "3.1.45.AABB", 37 | "contact": { 38 | "system": "phone", 39 | "value": "+1 (555) 123 4567" 40 | }, 41 | "endpoint": "llp:10.11.12.13:5432" 42 | }, 43 | "reason": { 44 | "coding": [ 45 | { 46 | "system": "http://terminology.hl7.org/CodeSystem/message-reasons-encounter", 47 | "code": "admit" 48 | } 49 | ] 50 | }, 51 | "response": { 52 | "identifier": { 53 | "value": "5015fe84-8e76-4526-89d8-44b322e8d4fb" 54 | }, 55 | "code": "ok" 56 | }, 57 | "focus": [ 58 | { 59 | "reference": "Patient/example" 60 | } 61 | ], 62 | "definition": "http:////acme.com/ehr/fhir/messagedefinition/patientrequest" 63 | } -------------------------------------------------------------------------------- /src/test/smoketest/smoketestfiles/patient_update.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "{{batch_patient_id}}", 4 | "text": { 5 | "status": "generated", 6 | "div": "
Some narrative
" 7 | }, 8 | "active": true, 9 | "name": [ 10 | { 11 | "use": "official", 12 | "family": "Iantoryes", 13 | "given": [ 14 | "Mark" 15 | ] 16 | } 17 | ], 18 | "gender": "male", 19 | "birthDate": "1983-06-23" 20 | } 21 | --------------------------------------------------------------------------------