diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 0000000000..bfaa195ac4 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,71 @@ +# This workflow runs a set of performance benchmarks that run and upload their results to a S3 +# bucket. + +name: Benchmark + +on: + workflow_dispatch: + +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + +jobs: + benchmark: + name: Benchmark + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # This is required so that git-commit-id-plugin can find the latest tag. + fetch-depth: 0 + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: "zulu" + - name: Cache local Maven repository + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: |- + ${{ runner.os }}-maven- + - name: Cache test data + id: cache-test-data + uses: actions/cache@v4 + with: + path: fhir-server/src/test/resources/test-data/parquet + key: ${{ runner.os }}-test-data-${{ hashFiles('fhir-server/src/test/resources/test-data/fhir/*.ndjson', 'encoders/src/main/**/*.java', 'encoders/src/main/**/*.scala', 'fhir-server/src/main/java/au/csiro/pathling/io/Database.java') }} + - name: Get current time + id: timestamp + run: echo "::set-output name=timestamp::$(date +'%Y%m%d%H%M%S')" + - name: Run the verify goal with Maven + env: + # If there is a cache hit on the test data, we don't need to import it. If it is a cache + # miss, we still need to explicitly activate the `importTestData` profile, otherwise + # it will be deactivated by `skipTests`. + PATHLING_PROFILES: runBenchmark,${{ steps.cache-test-data.outputs.cache-hit && '!importTestData' || 'importTestData' }} + run: >- + mvn --batch-mode verify + -pl fhir-server -am + -P${{ env.PATHLING_PROFILES }} + -DskipSurefireTests + -Dpathling.benchmark.testIterations=100 + -Dpathling.benchmark.warmupIterations=10 + timeout-minutes: 720 + - name: Upload benchmark artifact + uses: actions/upload-artifact@v4 + with: + name: benchmark-results + path: "**/jmh-*.json" + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::865780493209:role/PathlingBenchmarkUpload + aws-region: ap-southeast-2 + - name: Upload benchmark file to S3 + run: aws s3 sync fhir-server/target/benchmark s3://pathling-benchmark/${{ github.ref }}/${{ github.run_id }}/${{ steps.timestamp.outputs.timestamp }}/ diff --git a/.github/workflows/python-pre-release.yml b/.github/workflows/python-pre-release.yml index e570126eed..d371413dd3 100644 --- a/.github/workflows/python-pre-release.yml +++ b/.github/workflows/python-pre-release.yml @@ -10,23 +10,27 @@ on: required: true default: ".dev0" +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: deploy-python-pre-release: name: Python pre-release runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/release-js-client.yml b/.github/workflows/release-js-client.yml index 30908d7933..5b00f78156 100644 --- a/.github/workflows/release-js-client.yml +++ b/.github/workflows/release-js-client.yml @@ -9,23 +9,27 @@ on: tags: - 'pathling-client-v**' +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: deploy: name: NPM runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/release-js-import.yml b/.github/workflows/release-js-import.yml index 6487bae664..0f8a732838 100644 --- a/.github/workflows/release-js-import.yml +++ b/.github/workflows/release-js-import.yml @@ -9,23 +9,27 @@ on: tags: - 'pathling-import-v**' +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: deploy: name: NPM runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: 'zulu' - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 678fc53010..073ebd9b46 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,23 +13,27 @@ on: tags: - "v**" +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: deploy-maven: name: Maven Central runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -63,17 +67,17 @@ jobs: needs: deploy-maven steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -96,17 +100,17 @@ jobs: needs: deploy-python steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 66236570ac..3ab6716a3c 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -9,24 +9,28 @@ on: branches: - main +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: deploy: name: GitHub Pages runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Set up Helm uses: azure/setup-helm@v3 with: version: 3.13.2 - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3cc069dc88..f121e959cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,23 +19,27 @@ on: branches-ignore: - gh-pages +env: + # The add-exports and add-opens flags are required for Java 17 + MAVEN_OPTS: --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED + jobs: encoders: name: Encoders runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -52,17 +56,17 @@ jobs: ${{ env.PATHLING_OPTS }} timeout-minutes: 20 - name: Upload encoders test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: encoders-surefire-reports path: encoders/target/surefire-reports - name: Upload test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: encoders-coverage path: "**/jacoco.xml" - name: Upload dump files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: encoders-dump-files path: |- @@ -82,17 +86,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -100,7 +104,7 @@ jobs: ${{ runner.os }}-maven- - name: Cache test data id: cache-test-data - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: fhir-server/src/test/resources/test-data/parquet key: ${{ runner.os }}-test-data-${{ hashFiles('fhir-server/src/test/resources/test-data/fhir/*.ndjson', 'encoders/src/main/**/*.java', 'encoders/src/main/**/*.scala', 'fhir-server/src/main/java/au/csiro/pathling/io/Database.java') }} @@ -124,22 +128,22 @@ jobs: ${{ env.PATHLING_OPTS }} timeout-minutes: 30 - name: Upload fhir-server test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-1-surefire-reports path: fhir-server/target/surefire-reports - name: Upload test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-1-coverage path: "**/jacoco.xml" - name: Upload timing log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-1-timing-log path: fhir-server/target/timing.log - name: Upload dump files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-1-dump-files path: |- @@ -159,17 +163,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -177,7 +181,7 @@ jobs: ${{ runner.os }}-maven- - name: Cache test data id: cache-test-data - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: fhir-server/src/test/resources/test-data/parquet key: ${{ runner.os }}-test-data-${{ hashFiles('fhir-server/src/test/resources/test-data/fhir/*.ndjson', 'encoders/src/main/**/*.java', 'encoders/src/main/**/*.scala', 'fhir-server/src/main/java/au/csiro/pathling/io/Database.java') }} @@ -201,22 +205,22 @@ jobs: ${{ env.PATHLING_OPTS }} timeout-minutes: 30 - name: Upload fhir-server test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-2-surefire-reports path: fhir-server/target/surefire-reports - name: Upload test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-2-coverage path: "**/jacoco.xml" - name: Upload timing log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-2-timing-log path: fhir-server/target/timing.log - name: Upload dump files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-2-dump-files path: |- @@ -242,17 +246,17 @@ jobs: role-to-assume: arn:aws:iam::865780493209:role/PathlingBenchmarkUpload aws-region: ap-southeast-2 - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -260,7 +264,7 @@ jobs: ${{ runner.os }}-maven- - name: Cache test data id: cache-test-data - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: fhir-server/src/test/resources/test-data/parquet key: ${{ runner.os }}-test-data-${{ hashFiles('fhir-server/src/test/resources/test-data/fhir/*.ndjson', 'encoders/src/main/**/*.java', 'encoders/src/main/**/*.scala', 'fhir-server/src/main/java/au/csiro/pathling/io/Database.java') }} @@ -281,37 +285,37 @@ jobs: ${{ env.PATHLING_OPTS }} timeout-minutes: 30 - name: Upload utilities test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: utilities-surefire-reports path: utilities/target/surefire-reports - name: Upload terminology test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: terminology-surefire-reports path: terminology/target/surefire-reports - name: Upload FHIRPath test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhirpath-surefire-reports path: fhirpath/target/surefire-reports - name: Upload FHIR server test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-3-surefire-reports path: fhir-server/target/surefire-reports - name: Upload FHIR server test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-3-coverage path: "**/jacoco.xml" - name: Upload timing log - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-3-timing-log path: fhir-server/target/timing.log - name: Upload dump files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: fhir-server-3-dump-files path: |- @@ -334,17 +338,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -386,7 +390,7 @@ jobs: image-ref: "aehrc/pathling:${{ github.sha }}" exit-code: "1" ignore-unfixed: true - security-checks: "vuln" + scanners: "vuln" severity: CRITICAL timeout: 20m - name: Publish test results @@ -398,82 +402,25 @@ jobs: check_name: Docker image test report fail_on_test_failures: true - benchmark: - name: Benchmark - runs-on: ubuntu-latest - permissions: - id-token: write - steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - role-to-assume: arn:aws:iam::865780493209:role/PathlingBenchmarkUpload - aws-region: ap-southeast-2 - - name: Checkout code - uses: actions/checkout@v3 - with: - # This is required so that git-commit-id-plugin can find the latest tag. - fetch-depth: 0 - - name: Set up JDK - uses: actions/setup-java@v2 - with: - java-version: 11 - distribution: "zulu" - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: |- - ${{ runner.os }}-maven- - - name: Cache test data - id: cache-test-data - uses: actions/cache@v3 - with: - path: fhir-server/src/test/resources/test-data/parquet - key: ${{ runner.os }}-test-data-${{ hashFiles('fhir-server/src/test/resources/test-data/fhir/*.ndjson', 'encoders/src/main/**/*.java', 'encoders/src/main/**/*.scala', 'fhir-server/src/main/java/au/csiro/pathling/io/Database.java') }} - - name: Get current time - id: timestamp - run: echo "::set-output name=timestamp::$(date +'%Y%m%d%H%M%S')" - - name: Run the verify goal with Maven - env: - # If there is a cache hit on the test data, we don't need to import it. If it is a cache - # miss, we still need to explicitly activate the `importTestData` profile, otherwise - # it will be deactivated by `skipTests`. - PATHLING_PROFILES: runBenchmark,${{ steps.cache-test-data.outputs.cache-hit && '!importTestData' || 'importTestData' }} - run: >- - mvn --batch-mode verify - -pl fhir-server -am - -P${{ env.PATHLING_PROFILES }} - -DskipSurefireTests - timeout-minutes: 20 - - name: Upload benchmark artifact - uses: actions/upload-artifact@v3 - with: - name: benchmark-results - path: "**/jmh-*.json" - - name: Upload benchmark file to S3 - run: aws s3 sync fhir-server/target/benchmark s3://pathling-benchmark/${{ github.ref }}/${{ github.run_id }}/${{ steps.timestamp.outputs.timestamp }}/ - js-client: name: JavaScript client runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "16" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -490,20 +437,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "16" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -515,7 +462,7 @@ jobs: -pl lib/import -am timeout-minutes: 5 - name: Upload import lambda - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: import-lambda path: lib/import/target/pathling-import-lambda.zip @@ -525,17 +472,17 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} @@ -547,7 +494,7 @@ jobs: with: python-version: 3.8 - name: Cache Python packages - uses: actions/cache@v3 + uses: actions/cache@v4 id: pythoncache with: path: /home/runner/.cache/pip @@ -570,17 +517,17 @@ jobs: ${{ env.PATHLING_OPTS }} timeout-minutes: 30 - name: Upload library API test reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: library-api-surefire-reports path: library-api/target/surefire-reports - name: Upload library API test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: library-api-coverage path: "**/jacoco.xml" - name: Upload Python API test coverage report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: python-api-coverage path: lib/python/**/coverage.xml @@ -590,28 +537,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: |- ${{ runner.os }}-maven- - - name: Get R package installation location - run: echo "R_PACKAGES=$(Rscript -e 'cat(.libPaths(), sep="\n")' | head -n 1)" >> $GITHUB_ENV - name: Cache R packages uses: actions/cache@v2 with: - path: ${{ env.R_PACKAGES }} + path: ${{ runner.temp }}/Library key: r-packages-${{ runner.os }}-${{ hashFiles('lib/R/DESCRIPTION.src') }} restore-keys: r-packages-${{ runner.os }}- - name: Extract Spark version @@ -619,7 +564,7 @@ jobs: run: echo "SPARK_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.sparkVersion -q -DforceStdout)" >> $GITHUB_ENV - name: Extract Hadoop version working-directory: lib/R - run: echo "HADOOP_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.hadoopVersion -q -DforceStdout)" >> $GITHUB_ENV + run: echo "HADOOP_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.hadoopMajorVersion -q -DforceStdout)" >> $GITHUB_ENV - name: Cache Spark id: cache-spark uses: actions/cache@v2 @@ -652,6 +597,15 @@ jobs: -pl lib/R -am -Pdocs ${{ env.PATHLING_OPTS }} timeout-minutes: 60 + - name: Upload check logs + uses: actions/upload-artifact@v2 + if: always() + with: + name: r-check-logs + path: | + lib/R/target/pathling.Rcheck/*.log + lib/R/target/pathling.Rcheck/*.out + lib/R/target/pathling.Rcheck/*.fail - name: Upload package as artifact uses: actions/upload-artifact@v2 with: @@ -663,35 +617,41 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Set up Helm uses: azure/setup-helm@v3 with: version: 3.13.2 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "16" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: |- ${{ runner.os }}-maven- + - name: Cache R packages + uses: actions/cache@v2 + with: + path: ${{ runner.temp }}/Library + key: r-packages-${{ runner.os }}-${{ hashFiles('lib/R/DESCRIPTION.src') }} + restore-keys: r-packages-${{ runner.os }}- - name: Extract Spark version working-directory: lib/R run: echo "SPARK_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.sparkVersion -q -DforceStdout)" >> $GITHUB_ENV - name: Extract Hadoop version working-directory: lib/R - run: echo "HADOOP_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.hadoopVersion -q -DforceStdout)" >> $GITHUB_ENV + run: echo "HADOOP_VERSION=$(mvn help:evaluate -Dexpression=pathling.Rapi.hadoopMajorVersion -q -DforceStdout)" >> $GITHUB_ENV - name: Cache Spark id: cache-spark uses: actions/cache@v2 @@ -724,34 +684,34 @@ jobs: if: github.actor != 'dependabot[bot]' steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download encoders coverage report - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: encoders-coverage path: encoders-coverage - name: Download library API coverage report - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: library-api-coverage path: library-api-coverage - name: Download Python API coverage report - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: python-api-coverage path: python-api-coverage - name: Download FHIR server coverage report 1 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fhir-server-1-coverage path: fhir-server-1-coverage - name: Download FHIR server coverage report 2 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fhir-server-2-coverage path: fhir-server-2-coverage - name: Download FHIR server coverage report 3 - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: fhir-server-3-coverage path: fhir-server-3-coverage @@ -766,17 +726,17 @@ jobs: if: startsWith(github.ref, 'refs/heads/release/') steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # This is required so that git-commit-id-plugin can find the latest tag. fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: 17 distribution: "zulu" - name: Cache local Maven repository - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fef2d9a2f..5705afcd55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,7 +30,7 @@ Your branch should be named `issue/[GitHub issue #]`. You will need the following software to build the solution: -* Java 11 +* Java 17 * Maven 3+ * Node.js 19.x * Python 3.7+ @@ -53,8 +53,9 @@ follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). The "public API" of Pathling is defined as: 1. the FHIR API; -2. the public API of the encoders module; +2. the public API of the library API; 3. the public API of the Python library; +3. the public API of the R library; 4. the Parquet schema, and; 5. the configuration schema (see [Configuration](https://pathling.csiro.au/docs/configuration.html)). diff --git a/LICENSE b/LICENSE index 0e0ba8bf6b..6a50f430e4 100644 --- a/LICENSE +++ b/LICENSE @@ -183,61 +183,65 @@ agree to comply with the licence terms for these components as part of accessing the Software. Other third party software may also be identified in separate files distributed with the Software. -* (Apache License, Version 2.0) HAPI FHIR - Core Library (ca.uhn.hapi.fhir:hapi-fhir-base:6.10.0 - http://jamesagnew.github.io/hapi-fhir/) -* (Apache License, Version 2.0) HAPI FHIR - Client Framework (ca.uhn.hapi.fhir:hapi-fhir-client:6.10.0 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-client) -* (Apache License, Version 2.0) HAPI FHIR - Server Framework (ca.uhn.hapi.fhir:hapi-fhir-server:6.10.0 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-server) -* (Apache License, Version 2.0) HAPI FHIR Structures - FHIR R4 (ca.uhn.hapi.fhir:hapi-fhir-structures-r4:6.10.0 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-structures-r4) -* (Eclipse Public License 1.0) (GNU Lesser General Public License) Logback Classic Module (ch.qos.logback:logback-classic:1.2.11 - http://logback.qos.ch/logback-classic) +* (Apache License, Version 2.0) HAPI FHIR - Core Library (ca.uhn.hapi.fhir:hapi-fhir-base:7.0.2 - http://jamesagnew.github.io/hapi-fhir/) +* (Apache License, Version 2.0) HAPI FHIR - Client Framework (ca.uhn.hapi.fhir:hapi-fhir-client:7.0.2 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-client) +* (Apache License, Version 2.0) HAPI FHIR - Server Framework (ca.uhn.hapi.fhir:hapi-fhir-server:7.0.2 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-server) +* (Apache License, Version 2.0) HAPI FHIR Structures - FHIR R4 (ca.uhn.hapi.fhir:hapi-fhir-structures-r4:7.0.2 - https://hapifhir.io/hapi-deployable-pom/hapi-fhir-structures-r4) +* (Eclipse Public License 1.0) (GNU Lesser General Public License) Logback Classic Module (ch.qos.logback:logback-classic:1.4.14 - http://logback.qos.ch/logback-classic) * (Apache License, Version 2.0) AWS SDK for Java - Bundle (com.amazonaws:aws-java-sdk-bundle:1.12.603 - https://aws.amazon.com/sdkforjava) * (MIT License) java jwt (com.auth0:java-jwt:4.4.0 - https://github.com/auth0/java-jwt) -* (MIT License) jwks-rsa (com.auth0:jwks-rsa:0.22.0 - https://github.com/auth0/jwks-rsa-java) -* (Apache License, Version 2.0) Jackson-core (com.fasterxml.jackson.core:jackson-core:2.15.3 - https://github.com/FasterXML/jackson-core) +* (MIT License) jwks-rsa (com.auth0:jwks-rsa:0.22.1 - https://github.com/auth0/jwks-rsa-java) +* (Apache License, Version 2.0) Jackson-core (com.fasterxml.jackson.core:jackson-core:2.16.1 - https://github.com/FasterXML/jackson-core) * (Apache License, Version 2.0) Woodstox (com.fasterxml.woodstox:woodstox-core:6.4.0 - https://github.com/FasterXML/woodstox) * (Apache License, Version 2.0) docker-java (com.github.docker-java:docker-java:3.3.4 - https://github.com/docker-java/docker-java) * (Apache License, Version 2.0) docker-java-transport-okhttp (com.github.docker-java:docker-java-transport-okhttp:3.3.4 - https://github.com/docker-java/docker-java) -* (Apache License, Version 2.0) WireMock (com.github.tomakehurst:wiremock-jre8-standalone:2.35.0 - http://wiremock.org) +* (Apache License, Version 2.0) WireMock (com.github.tomakehurst:wiremock-jre8-standalone:2.35.2 - http://wiremock.org) * (Apache License, Version 2.0) FindBugs-jsr305 (com.google.code.findbugs:jsr305:3.0.2 - http://findbugs.sourceforge.net/) * (Apache License, Version 2.0) Gson (com.google.code.gson:gson:2.10 - https://github.com/google/gson/gson) * (Apache License, Version 2.0) Guava: Google Core Libraries for Java (com.google.guava:guava:32.1.3-jre - https://github.com/google/guava) * (Apache License, Version 2.0) Apache Commons BeanUtils (commons-beanutils:commons-beanutils:1.9.4 - https://commons.apache.org/proper/commons-beanutils/) -* (Apache License, Version 2.0) Apache Commons IO (commons-io:commons-io:2.13.0 - https://commons.apache.org/proper/commons-io/) +* (Apache License, Version 2.0) Apache Commons IO (commons-io:commons-io:2.16.1 - https://commons.apache.org/proper/commons-io/) * (Apache License, Version 2.0) Commons Lang (commons-lang:commons-lang:2.6 - http://commons.apache.org/lang/) -* (Apache License, Version 2.0) delta-core (io.delta:delta-core_2.12:2.4.0 - https://delta.io/) -* (Apache License, Version 2.0) micrometer-registry-prometheus (io.micrometer:micrometer-registry-prometheus:1.11.3 - https://github.com/micrometer-metrics/micrometer) +* (Apache License, Version 2.0) delta-spark (io.delta:delta-spark_2.12:3.2.0 - https://delta.io/) +* (Apache License, Version 2.0) micrometer-registry-prometheus (io.micrometer:micrometer-registry-prometheus:1.13.0 - https://github.com/micrometer-metrics/micrometer) * (MIT License) Sentry SDK (io.sentry:sentry:6.6.0 - https://github.com/getsentry/sentry-java) -* (Apache License, Version 2.0) Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:2.0.2 - https://beanvalidation.org) -* (Apache License, Version 2.0) Joda-Time (joda-time:joda-time:2.12.5 - https://www.joda.org/joda-time/) +* (Apache License, Version 2.0) Jakarta Bean Validation API (jakarta.validation:jakarta.validation-api:3.0.2 - https://beanvalidation.org) +* (CDDL + GPLv2 with classpath exception) Java Servlet API (javax.servlet:javax.servlet-api:4.0.1 - https://javaee.github.io/servlet-spec/) +* (Apache License, Version 2.0) Joda-Time (joda-time:joda-time:2.12.7 - https://www.joda.org/joda-time/) * (BSD License) ANTLR 4 Tool (org.antlr:antlr4:4.9.3 - http://www.antlr.org) -* (Apache License, Version 2.0) Apache Commons Lang (org.apache.commons:commons-lang3:3.12.0 - https://commons.apache.org/proper/commons-lang/) +* (Apache License, Version 2.0) Apache Commons Lang (org.apache.commons:commons-lang3:3.14.0 - https://commons.apache.org/proper/commons-lang/) +* (Apache License, Version 2.0) Apache Derby Tools (org.apache.derby:derbytools:10.14.2.0 - http://db.apache.org/derby/) * (Apache License, Version 2.0) Apache Hadoop Amazon Web Services support (org.apache.hadoop:hadoop-aws:3.3.4 - no url defined) * (Apache License, Version 2.0) Apache Hadoop Client API (org.apache.hadoop:hadoop-client-api:3.3.4 - no url defined) -* (Apache License, Version 2.0) Spark Project Catalyst (org.apache.spark:spark-catalyst_2.12:3.4.1 - https://spark.apache.org/) -* (Apache License, Version 2.0) Spark Project Core (org.apache.spark:spark-core_2.12:3.4.1 - https://spark.apache.org/) -* (Apache License, Version 2.0) Spark Project Hive (org.apache.spark:spark-hive_2.12:3.4.1 - https://spark.apache.org/) -* (Apache License, Version 2.0) Spark Project Kubernetes (org.apache.spark:spark-kubernetes_2.12:3.4.1 - https://spark.apache.org/spark-kubernetes_2.12/) -* (Apache License, Version 2.0) Spark Project SQL (org.apache.spark:spark-sql_2.12:3.4.1 - https://spark.apache.org/) -* (Apache License, Version 2.0) Spark Project Unsafe (org.apache.spark:spark-unsafe_2.12:3.4.1 - https://spark.apache.org/) +* (Apache License, Version 2.0) Spark Project Catalyst (org.apache.spark:spark-catalyst_2.12:3.5.0 - https://spark.apache.org/) +* (Apache License, Version 2.0) Spark Project Core (org.apache.spark:spark-core_2.12:3.5.0 - https://spark.apache.org/) +* (Apache License, Version 2.0) Spark Project Hive (org.apache.spark:spark-hive_2.12:3.5.0 - https://spark.apache.org/) +* (Apache License, Version 2.0) Spark Project Kubernetes (org.apache.spark:spark-kubernetes_2.12:3.5.0 - https://spark.apache.org/spark-kubernetes_2.12/) +* (Apache License, Version 2.0) Spark Project SQL (org.apache.spark:spark-sql_2.12:3.5.0 - https://spark.apache.org/) +* (Apache License, Version 2.0) Spark Project Unsafe (org.apache.spark:spark-unsafe_2.12:3.5.0 - https://spark.apache.org/) * (Apache License, Version 2.0) Awaitility (org.awaitility:awaitility:4.2.0 - http://awaitility.org) -* (Apache License, Version 2.0) Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:6.0.13.Final - http://hibernate.org/validator/hibernate-validator) -* (Apache License, Version 2.0) Infinispan Commons (org.infinispan:infinispan-commons:14.0.21.Final - http://www.infinispan.org/infinispan-commons-parent/infinispan-commons) -* (Apache License, Version 2.0) Infinispan Core (org.infinispan:infinispan-core:14.0.21.Final - http://www.infinispan.org/infinispan-core) -* (Public Domain) JSON in Java (org.json:json:20230618 - https://github.com/douglascrockford/JSON-java) -* (Eclipse Public License v2.0) JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.9.1 - https://junit.org/junit5/) -* (Eclipse Public License v2.0) JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.9.1 - https://junit.org/junit5/) -* (Eclipse Public License v2.0) JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.9.1 - https://junit.org/junit5/) -* (MIT License) mockito-core (org.mockito:mockito-core:4.8.1 - https://github.com/mockito/mockito) -* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Core (org.openjdk.jmh:jmh-core:1.36 - http://openjdk.java.net/projects/code-tools/jmh/jmh-core/) -* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Generators: Annotation Processors (org.openjdk.jmh:jmh-generator-annprocess:1.36 - http://openjdk.java.net/projects/code-tools/jmh/jmh-generator-annprocess/) -* (MIT License) Project Lombok (org.projectlombok:lombok:1.18.28 - https://projectlombok.org) +* (Apache License, Version 2.0) Hibernate Validator Engine (org.hibernate.validator:hibernate-validator:8.0.1.Final - http://hibernate.org/validator/hibernate-validator) +* (Apache License, Version 2.0) Infinispan Commons (org.infinispan:infinispan-commons:15.0.3.Final - http://www.infinispan.org) +* (Apache License, Version 2.0) Infinispan Component Annotations (org.infinispan:infinispan-component-annotations:15.0.3.Final - http://www.infinispan.org) +* (Apache License, Version 2.0) Infinispan Core (org.infinispan:infinispan-core:15.0.3.Final - http://www.infinispan.org) +* (Public Domain) JSON in Java (org.json:json:20240303 - https://github.com/douglascrockford/JSON-java) +* (Eclipse Public License v2.0) JUnit Jupiter API (org.junit.jupiter:junit-jupiter-api:5.10.1 - https://junit.org/junit5/) +* (Eclipse Public License v2.0) JUnit Jupiter Engine (org.junit.jupiter:junit-jupiter-engine:5.10.1 - https://junit.org/junit5/) +* (Eclipse Public License v2.0) JUnit Jupiter Params (org.junit.jupiter:junit-jupiter-params:5.10.1 - https://junit.org/junit5/) +* (MIT License) mockito-core (org.mockito:mockito-core:5.8.0 - https://github.com/mockito/mockito) +* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Core (org.openjdk.jmh:jmh-core:1.37 - http://openjdk.java.net/projects/code-tools/jmh/jmh-core/) +* (GNU General Public License (GPL), version 2, with the Classpath exception) JMH Generators: Annotation Processors (org.openjdk.jmh:jmh-generator-annprocess:1.37 - http://openjdk.java.net/projects/code-tools/jmh/jmh-generator-annprocess/) +* (MIT License) Project Lombok (org.projectlombok:lombok:1.18.32 - https://projectlombok.org) * (Apache License, Version 2.0) Scala Library (org.scala-lang:scala-library:2.12.17 - https://www.scala-lang.org/) * (Apache License, Version 2.0) JSONassert (org.skyscreamer:jsonassert:1.5.1 - https://github.com/skyscreamer/JSONassert) -* (MIT License) SLF4J API Module (org.slf4j:slf4j-api:1.7.36 - http://www.slf4j.org) -* (Apache License, Version 2.0) spring-boot-configuration-processor (org.springframework.boot:spring-boot-configuration-processor:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:2.7.18 - https://spring.io/projects/spring-boot) -* (Apache License, Version 2.0) spring-security-oauth2-jose (org.springframework.security:spring-security-oauth2-jose:5.7.10 - https://spring.io/projects/spring-security) -* (Apache License, Version 2.0) spring-security-oauth2-resource-server (org.springframework.security:spring-security-oauth2-resource-server:5.7.10 - https://spring.io/projects/spring-security) -* (Apache License, Version 2.0) spring-security-test (org.springframework.security:spring-security-test:5.7.10 - https://spring.io/projects/spring-security) +* (MIT License) SLF4J API Module (org.slf4j:slf4j-api:2.0.9 - http://www.slf4j.org) +* (Apache License, Version 2.0) spring-boot-configuration-processor (org.springframework.boot:spring-boot-configuration-processor:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-actuator (org.springframework.boot:spring-boot-starter-actuator:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-aop (org.springframework.boot:spring-boot-starter-aop:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-jetty (org.springframework.boot:spring-boot-starter-jetty:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-security (org.springframework.boot:spring-boot-starter-security:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-test (org.springframework.boot:spring-boot-starter-test:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-boot-starter-web (org.springframework.boot:spring-boot-starter-web:3.2.0 - https://spring.io/projects/spring-boot) +* (Apache License, Version 2.0) spring-security-oauth2-jose (org.springframework.security:spring-security-oauth2-jose:6.2.0 - https://spring.io/projects/spring-security) +* (Apache License, Version 2.0) spring-security-oauth2-resource-server (org.springframework.security:spring-security-oauth2-resource-server:6.2.0 - https://spring.io/projects/spring-security) +* (Apache License, Version 2.0) spring-security-test (org.springframework.security:spring-security-test:6.2.0 - https://spring.io/projects/spring-security) diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md new file mode 100644 index 0000000000..d95fab3b47 --- /dev/null +++ b/RELEASE_CHECKLIST.md @@ -0,0 +1,52 @@ +# Release checklist + +- [ ] Create a release branch (`release/[version]`) +- [ ] Update the version to a SNAPSHOT version for pre-release activities +- [ ] Update all dependencies +- [ ] Get all tests and checks passing on CI ("Test" workflow) +- [ ] Create a Python pre-release ("Python pre-release" workflow, manually + triggered) +- [ ] Test snapshot library API, dev Python library release and R package on + target Databricks release +- [ ] Deploy pre-release Docker image to demo cluster and test +- [ ] Update the supported Databricks Runtime versions in the + documentation (`site/docs/libraries/installation/databricks.md`) +- [ ] Update licenses (`mvn install -DskipTests -Plicenses`) +- [ ] Update the version to the release version +- [ ] Tag the release (`v[version]`) +- [ ] Merge to main and make sure it is green +- [ ] Create a GitHub release for the tag, describe new features, bug fixes and + breaking changes +- [ ] Submit the built R package to CRAN manually + +## Updating the version + +The version number needs to be changed in the following places: + +- [ ] All POM files (version of the main POM, references to parent in all child + POMs) +- [ ] `lib/python/Dockerfile` +- [ ] `docs/libraries/installation/spark.md` + +## JavaScript libraries + +The JavaScript libraries (`lib/import` and `lib/js`) are versioned independently +of the rest of the project. To release changes to these libraries, follow these +steps: + +- [ ] Update the version in `lib/import/package.json` + and/or `lib/js/package.json` (using `npm version`) +- [ ] Tag the repository with `pathling-import-v[version]` + and/or `pathling-client-v[version]` +- [ ] Push the tag to GitHub + +## Helm chart + +The Helm chart is versioned independently of the rest of the project. The Helm +chart publishes the chart to the web site based upon the metadata in +the `Chart.yaml` file. + +It only currently supports the publishing of the current version of the chart. + +No tagging is required to release the chart, it is built and published upon +every execution of the "Deploy site" workflow. diff --git a/deployment/helm/pathling/Chart.yaml b/deployment/helm/pathling/Chart.yaml index 82fc23febf..1bfa669e3a 100644 --- a/deployment/helm/pathling/Chart.yaml +++ b/deployment/helm/pathling/Chart.yaml @@ -3,7 +3,7 @@ name: pathling description: A Helm chart for Pathling Server icon: https://raw.githubusercontent.com/aehrc/pathling/main/media/logo-icon-colour-detail.svg type: application -version: 1.0.0 +version: 1.0.2 maintainers: - name: John Grimes email: John.Grimes@csiro.au diff --git a/deployment/helm/pathling/README.md b/deployment/helm/pathling/README.md index a66309c857..8a7820f802 100644 --- a/deployment/helm/pathling/README.md +++ b/deployment/helm/pathling/README.md @@ -75,7 +75,7 @@ scenario, all processing is performed on a single pod. ```yml pathling: - image: aehrc/pathling:6 + image: aehrc/pathling:7 resources: requests: cpu: 2 @@ -113,7 +113,7 @@ pods on demand (at the cost of some latency). ```yml pathling: - image: aehrc/pathling:6 + image: aehrc/pathling:7 resources: requests: cpu: 1 @@ -138,7 +138,7 @@ pathling: logging.level.au.csiro.pathling: debug spark.master: k8s://https://kubernetes.default.svc spark.kubernetes.namespace: pathling - spark.kubernetes.executor.container.image: aehrc/pathling:6 + spark.kubernetes.executor.container.image: aehrc/pathling:7 spark.kubernetes.executor.volumes.hostPath.warehouse.options.path: /home/user/data/pathling spark.kubernetes.executor.volumes.hostPath.warehouse.mount.path: /usr/share/warehouse spark.kubernetes.executor.volumes.hostPath.warehouse.mount.readOnly: false diff --git a/deployment/helm/pathling/templates/deployment.yaml b/deployment/helm/pathling/templates/deployment.yaml index 66b0ad15f6..3e0a11fff9 100644 --- a/deployment/helm/pathling/templates/deployment.yaml +++ b/deployment/helm/pathling/templates/deployment.yaml @@ -46,7 +46,7 @@ spec: {{- if gt (add (len .Values.pathling.config) (len .Values.pathling.secretConfig)) 0 }} env: - name: JAVA_TOOL_OPTIONS - value: {{ printf "-Xmx%s %s" .Values.pathling.resources.maxHeapSize .Values.pathling.additionalJavaOptions }} + value: {{ printf "-Xmx%s --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED %s" .Values.pathling.resources.maxHeapSize .Values.pathling.additionalJavaOptions }} - name: spark.driver.host value: pathling-driver - name: spark.driver.port diff --git a/deployment/helm/pathling/templates/service.yaml b/deployment/helm/pathling/templates/service.yaml index 0f8bf0a94c..d19bc4fdd7 100644 --- a/deployment/helm/pathling/templates/service.yaml +++ b/deployment/helm/pathling/templates/service.yaml @@ -3,7 +3,7 @@ apiVersion: v1 metadata: name: pathling-fhir spec: - type: ClusterIP + type: NodePort ports: - port: 8080 targetPort: 8080 diff --git a/encoders/pom.xml b/encoders/pom.xml index cb406fb35c..1964678a50 100644 --- a/encoders/pom.xml +++ b/encoders/pom.xml @@ -84,6 +84,10 @@ spark-unsafe_${pathling.scalaVersion} provided + + javax.servlet + javax.servlet-api + @@ -204,6 +208,11 @@ + + + **/logback.xml + + diff --git a/encoders/src/license/THIRD-PARTY.properties b/encoders/src/license/THIRD-PARTY.properties index f17c8e7aae..1a72053020 100644 --- a/encoders/src/license/THIRD-PARTY.properties +++ b/encoders/src/license/THIRD-PARTY.properties @@ -18,13 +18,16 @@ # - BSD 3 Clause # - BSD 3-clause # - BSD-3-Clause +# - CDDL + GPLv2 with classpath exception # - COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 # - EDL 1.0 # - EPL 2.0 +# - EPL-2.0 # - Eclipse Public License - v 1.0 # - Eclipse Public License v2.0 # - GNU General Public License, version 2 # - GNU Lesser General Public License +# - GPL-2.0-with-classpath-exception # - GPL2 w/ CPE # - Indiana University Extreme! Lab Software License, vesion 1.1.1 # - LGPL 2.1 @@ -50,5 +53,5 @@ # Please fill the missing licenses for dependencies : # # -#Fri Dec 01 16:32:30 AEST 2023 +#Mon May 20 15:42:34 AEST 2024 oro--oro--2.0.8= diff --git a/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java b/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java index 0dbf89b9fc..e1cf3c2c8c 100644 --- a/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java +++ b/encoders/src/main/java/au/csiro/pathling/config/EncodingConfiguration.java @@ -23,9 +23,9 @@ package au.csiro.pathling.config; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import java.util.Set; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; import au.csiro.pathling.encoders.FhirEncoders; import lombok.Builder; import lombok.Data; diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/Catalyst.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/Catalyst.scala new file mode 100644 index 0000000000..3afaa88da2 --- /dev/null +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/Catalyst.scala @@ -0,0 +1,55 @@ +package au.csiro.pathling.encoders + +import org.apache.spark.sql.catalyst.expressions.Expression +import org.apache.spark.sql.catalyst.expressions.objects.StaticInvoke +import org.apache.spark.sql.types.DataType + + +/** + * This class provides a compatibility layer for Spark Catalyst. + * It is used to createnew Expressions using constructors compatible with the runtime + * version of Spark Catalyst. + * This is necessary to address running Pathling in Databricks environments, + * which habitually use different (newer) version of Catalyst that one used by + * the corresponding Spark public release. + */ +object Catalyst { + + private lazy val staticInvokeAdapter: (Class[_], DataType, String, Seq[Expression]) => StaticInvoke = { + val constructor = classOf[StaticInvoke].getConstructors.head + if (constructor.getParameterCount == 8) { + // catalyst 3.5.x (used by Spark 3.5.x) + StaticInvoke.apply(_, _, _, _) + } else if (constructor.getParameterCount == 9) { + // catalyst 4.0.0-preview-rc1 (used by Databricks runtime 14.3 LTS with Spark 3.5.0) + (staticObject: Class[_], dataType: DataType, functionName: String, arguments: Seq[Expression]) => + constructor.newInstance(staticObject, dataType, functionName, + arguments, + Nil, + Boolean.box(true), + Boolean.box(true), + Boolean.box(true), + None).asInstanceOf[StaticInvoke] + } else { + throw new IllegalStateException( + "Unsupported version of Spark Catalyst with InvokeStatic constructor: " + constructor) + } + } + + /** + * Creates a new [[StaticInvoke]] expression using a constructor compatible with the runtime version of Spark Catalyst. + * + * @param staticObject the class object to invoke. + * @param dataType the return type of the function. + * @param functionName the name of the function to invoke. + * @param arguments the arguments to pass to the function. + * @return the new [[StaticInvoke]] expression. + */ + def staticInvoke( + staticObject: Class[_], + dataType: DataType, + functionName: String, + arguments: Seq[Expression] = Nil): StaticInvoke = { + staticInvokeAdapter(staticObject, dataType, functionName, arguments) + } +} diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/EncoderUtils.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/EncoderUtils.scala index 9074ba3781..95bbde9df4 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/EncoderUtils.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/EncoderUtils.scala @@ -37,7 +37,7 @@ object EncoderUtils { } def arrayExpression(array: Expression): StaticInvoke = { - StaticInvoke( + Catalyst.staticInvoke( classOf[util.Arrays], ObjectType(classOf[util.List[_]]), "asList", diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala index a2862ff3f7..7b243bd56b 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/Expressions.scala @@ -112,6 +112,54 @@ case class InstanceOf(value: Expression, } + +/** + * Gets value of an element of a HAPI object. This is could be composed from `If` and `Invoke` + * expression but having it as a dedicated expression makes the serializer expression more readable + * and also avoids the problems with 'If' optimisations. + * + * @param value the expression with the reference to the HAPI object + * @param dataType the data type of the element to get + * @param hasMethod the name of method to check if the element is present, e.g. hasNameElement() + * @param getMethod the name of method to get the value of the element, e.g. getNameElement() + * @return the value of the element or null if the element is not present or the object is null + */ +case class GetHapiValue(value: Expression, + dataType: DataType, + hasMethod: String, getMethod: String) + extends Expression with NonSQLExpression { + + override def nullable: Boolean = true + + override def children: Seq[Expression] = value :: Nil + + override def eval(input: InternalRow): Any = + throw new UnsupportedOperationException("Only code-generated evaluation is supported.") + + override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = { + + val obj = value.genCode(ctx) + val javaType = CodeGenerator.javaType(dataType) + val code = + code""" + |// BEGIN: GetHapiValue + |${obj.code} + |boolean ${ev.isNull} = true; + |$javaType ${ev.value} = ${CodeGenerator.defaultValue(dataType)}; + |if (!${obj.isNull} && ${obj.value}.$hasMethod()) { + | ${ev.isNull} = false; + | ${ev.value} = ${obj.value}.$getMethod(); + |} + |// END: GetHapiValue + """.stripMargin + ev.copy(code = code) + } + + override protected def withNewChildrenInternal(newChildren: IndexedSeq[Expression]): Expression = { + GetHapiValue(newChildren.head, dataType, hasMethod, getMethod) + } +} + /** * Casts the result of an expression to another type. * diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/QuantitySupport.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/QuantitySupport.scala index ad789d07c2..9efc513f2f 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/QuantitySupport.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/QuantitySupport.scala @@ -26,11 +26,9 @@ package au.csiro.pathling.encoders import au.csiro.pathling.encoders.terminology.ucum.Ucum import au.csiro.pathling.sql.types.FlexiDecimal import au.csiro.pathling.sql.types.FlexiDecimalSupport.createFlexiDecimalSerializer -import org.apache.spark.sql.Column +import org.apache.spark.sql.catalyst.expressions.Expression import org.apache.spark.sql.catalyst.expressions.objects.{Invoke, StaticInvoke} -import org.apache.spark.sql.catalyst.expressions.{CreateNamedStruct, Expression, If, IsNull, Literal} -import org.apache.spark.sql.functions.{lit, struct} -import org.apache.spark.sql.types.{DataTypes, Decimal, DecimalType, ObjectType, StructField} +import org.apache.spark.sql.types.{DataTypes, ObjectType, StructField} import org.apache.spark.unsafe.types.UTF8String /** @@ -54,15 +52,15 @@ object QuantitySupport { val canonicalizedValue = createFlexiDecimalSerializer( - StaticInvoke(classOf[Ucum], ObjectType(classOf[java.math.BigDecimal]), + Catalyst.staticInvoke(classOf[Ucum], ObjectType(classOf[java.math.BigDecimal]), "getCanonicalValue", Seq(valueExp, codeExp))) val canonicalizedCode = - StaticInvoke( + Catalyst.staticInvoke( classOf[UTF8String], DataTypes.StringType, "fromString", - StaticInvoke(classOf[Ucum], ObjectType(classOf[java.lang.String]), "getCanonicalCode", + Catalyst.staticInvoke(classOf[Ucum], ObjectType(classOf[java.lang.String]), "getCanonicalCode", Seq(valueExp, codeExp)) :: Nil) Seq( (VALUE_CANONICALIZED_FIELD_NAME, canonicalizedValue), diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaConverter.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaConverter.scala index 96bfacce85..2ca837c0cc 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaConverter.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaConverter.scala @@ -24,8 +24,7 @@ package au.csiro.pathling.encoders import au.csiro.pathling.encoders.ExtensionSupport.{EXTENSIONS_FIELD_NAME, FID_FIELD_NAME} -import au.csiro.pathling.encoders.QuantitySupport.{CODE_CANONICALIZED_FIELD_NAME, VALUE_CANONICALIZED_FIELD_NAME} -import au.csiro.pathling.encoders.datatypes.{DataTypeMappings, DecimalCustomCoder} +import au.csiro.pathling.encoders.datatypes.DataTypeMappings import au.csiro.pathling.schema.SchemaVisitor import au.csiro.pathling.schema.SchemaVisitor.isCollection import ca.uhn.fhir.context._ diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaProcessor.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaProcessor.scala index 731b05f58e..d5a1b30dc0 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaProcessor.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/SchemaProcessor.scala @@ -24,6 +24,7 @@ package au.csiro.pathling.encoders import au.csiro.pathling.schema._ import ca.uhn.fhir.context._ +import org.hl7.fhir.instance.model.api.IBaseReference /** @@ -95,8 +96,20 @@ trait SchemaProcessor[DT, SF] extends SchemaVisitor[DT, SF] with EncoderSettings } } + private def includeElement(elementDefinition: BaseRuntimeElementDefinition[_]): Boolean = { + val nestingLevel = EncodingContext.currentNestingLevel(elementDefinition) + if (classOf[IBaseReference].isAssignableFrom(elementDefinition.getImplementingClass)) { + // This is a special provision for References which disallows any nesting. + // It removes the `assigner` field from the Identifier type instances + // nested inside a Reference (in its `identifier` element). + nestingLevel <= 0 + } else { + nestingLevel <= maxNestingLevel + } + } + override def visitElement(elementCtx: ElementCtx[DT, SF]): Seq[SF] = { - if (EncodingContext.currentNestingLevel(elementCtx.elementDefinition) <= maxNestingLevel) { + if (includeElement(elementCtx.elementDefinition)) { buildValue(elementCtx.childDefinition, elementCtx.elementDefinition, elementCtx.elementName) } else { Nil diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/SerializerBuilder.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/SerializerBuilder.scala index 25e4489ab2..c057fcca22 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/SerializerBuilder.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/SerializerBuilder.scala @@ -24,10 +24,8 @@ package au.csiro.pathling.encoders import au.csiro.pathling.encoders.ExtensionSupport.{EXTENSIONS_FIELD_NAME, FID_FIELD_NAME} -import au.csiro.pathling.encoders.QuantitySupport.{CODE_CANONICALIZED_FIELD_NAME, VALUE_CANONICALIZED_FIELD_NAME} import au.csiro.pathling.encoders.SerializerBuilderProcessor.{dataTypeToUtf8Expr, getChildExpression, objectTypeFor} -import au.csiro.pathling.encoders.datatypes.{DataTypeMappings, DecimalCustomCoder} -import au.csiro.pathling.encoders.terminology.ucum.Ucum +import au.csiro.pathling.encoders.datatypes.DataTypeMappings import au.csiro.pathling.schema.SchemaVisitor.isCollection import au.csiro.pathling.schema._ import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum @@ -36,7 +34,7 @@ import org.apache.spark.sql.catalyst.expressions.objects.{ExternalMapToCatalyst, import org.apache.spark.sql.catalyst.expressions.{BoundReference, CreateNamedStruct, Expression, If, IsNull, Literal} import org.apache.spark.sql.types._ import org.apache.spark.unsafe.types.UTF8String -import org.hl7.fhir.instance.model.api.{IBaseDatatype, IBaseHasExtensions, IBaseResource} +import org.hl7.fhir.instance.model.api.{IBaseDatatype, IBaseHasExtensions, IBaseReference, IBaseResource} import org.hl7.fhir.r4.model.{Base, Extension, Quantity} import org.hl7.fhir.utilities.xhtml.XhtmlNode @@ -97,7 +95,7 @@ private[encoders] class SerializerBuilderProcessor(expression: Expression, private def createExtensionsFields(definition: BaseRuntimeElementCompositeDefinition[_]): Seq[(String, Expression)] = { val maybeExtensionValueField = definition match { case _: RuntimeResourceDefinition => - val collectExtensionsExpression = StaticInvoke( + val collectExtensionsExpression = Catalyst.staticInvoke( classOf[SerializerBuilderProcessor], ObjectType(classOf[Map[Int, java.util.List[Extension]]]), "flattenExtensions", @@ -113,7 +111,7 @@ private[encoders] class SerializerBuilderProcessor(expression: Expression, case _ => Nil } // append _fid serializer - (FID_FIELD_NAME, StaticInvoke( + (FID_FIELD_NAME, Catalyst.staticInvoke( classOf[System], IntegerType, "identityHashCode", expression :: Nil)) :: maybeExtensionValueField } @@ -190,11 +188,12 @@ private[encoders] object SerializerBuilderProcessor { private def getChildExpression(parentObject: Expression, childDefinition: BaseRuntimeChildDefinition, dataType: DataType): Expression = { - Invoke(parentObject, - accessorFor(childDefinition), - dataType) + + val (hasMethod, getMethod) = accessorsFor(childDefinition) + GetHapiValue(parentObject, dataType, hasMethod, getMethod) } + private def getChildExpression(parentObject: Expression, childDefinition: BaseRuntimeChildDefinition): Expression = { @@ -209,7 +208,7 @@ private[encoders] object SerializerBuilderProcessor { } private def dataTypeToUtf8Expr(inputObject: Expression): Expression = { - StaticInvoke( + Catalyst.staticInvoke( classOf[UTF8String], DataTypes.StringType, "fromString", @@ -219,23 +218,39 @@ private[encoders] object SerializerBuilderProcessor { } /** - * Returns the accessor method for the given child field. + * Returns the accessor methods for the given child field. + * These are the 'has' and 'get' methods that test for the presence of the field an retrieve its value. + * + * @param field the runtime child definition of the field + * @return a tuple containing the names of 'has' and 'get' methods */ - private def accessorFor(field: BaseRuntimeChildDefinition): String = { + private def accessorsFor(field: BaseRuntimeChildDefinition): (String, String) = { + + def defaultAccessors(suffix: String): (String, String) = { + ("has" + suffix, "get" + suffix) + } // Primitive single-value types typically use the Element suffix in their // accessors, with the exception of the "div" field for reasons that are not clear. //noinspection DuplicatedCode - if (field.isInstanceOf[RuntimeChildPrimitiveDatatypeDefinition] && - field.getMax == 1 && - field.getElementName != "div") - "get" + field.getElementName.capitalize + "Element" - else { - if (field.getElementName.equals("class")) { - "get" + field.getElementName.capitalize + "_" - } else { - "get" + field.getElementName.capitalize - } + field match { + case p: RuntimeChildPrimitiveDatatypeDefinition if p.getMax == 1 && p + .getElementName != "div" => + if ("reference" == p.getElementName && classOf[IBaseReference] + .isAssignableFrom(p.getField.getDeclaringClass)) { + // Special case for subclasses of IBaseReference. + // The accessor getReferenceElement returns IdType rather than + // StringType and getReferenceElement_ needs to be used instead. + // All subclasses of IBaseReference have a getReferenceElement_ + // method. + ("hasReferenceElement", "getReferenceElement_") + } else { + defaultAccessors(p.getElementName.capitalize + "Element") + } + case f if f.getElementName.equals("class") => + defaultAccessors(f.getElementName.capitalize + "_") + case _ => + defaultAccessors(field.getElementName.capitalize) } } diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DataTypeMappings.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DataTypeMappings.scala index 326c28eb94..51fcda1af7 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DataTypeMappings.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DataTypeMappings.scala @@ -23,7 +23,7 @@ package au.csiro.pathling.encoders.datatypes -import au.csiro.pathling.encoders.ExpressionWithName +import au.csiro.pathling.encoders.{Catalyst, ExpressionWithName} import ca.uhn.fhir.context._ import org.apache.spark.sql.catalyst.expressions.Expression import org.apache.spark.sql.catalyst.expressions.objects.{Invoke, StaticInvoke} @@ -60,7 +60,7 @@ trait DataTypeMappings { */ def dataTypeToUtf8Expr(inputObject: Expression): Expression = { - StaticInvoke( + Catalyst.staticInvoke( classOf[UTF8String], DataTypes.StringType, "fromString", diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DecimalCustomCoder.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DecimalCustomCoder.scala index eb7ef335d8..f24e1e7c04 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DecimalCustomCoder.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/DecimalCustomCoder.scala @@ -24,7 +24,7 @@ package au.csiro.pathling.encoders.datatypes import au.csiro.pathling.encoders.EncoderUtils.arrayExpression -import au.csiro.pathling.encoders.ExpressionWithName +import au.csiro.pathling.encoders.{Catalyst, ExpressionWithName} import au.csiro.pathling.encoders.datatypes.DecimalCustomCoder.decimalType import org.apache.spark.sql.catalyst.expressions.objects.{Invoke, NewInstance, StaticInvoke} import org.apache.spark.sql.catalyst.expressions.{Expression, Literal} @@ -54,7 +54,7 @@ case class DecimalCustomCoder(elementName: String) extends CustomCoder { decimalExpression(addToPath) } else { // Let's to this manually as we need to zip two independent arrays into into one - val array = StaticInvoke( + val array = Catalyst.staticInvoke( classOf[DecimalCustomCoder], ObjectType(classOf[Array[Any]]), "zipToDecimal", @@ -66,7 +66,7 @@ case class DecimalCustomCoder(elementName: String) extends CustomCoder { } override def customSerializer(evaluator: (Expression => Expression) => Expression): Seq[ExpressionWithName] = { - val valueExpression = evaluator(exp => StaticInvoke(classOf[Decimal], + val valueExpression = evaluator(exp => Catalyst.staticInvoke(classOf[Decimal], decimalType, "apply", Invoke(exp, "getValue", ObjectType(classOf[java.math.BigDecimal])) :: Nil)) @@ -95,7 +95,7 @@ case class DecimalCustomCoder(elementName: String) extends CustomCoder { } private def scaleExpression(inputObject: Expression) = { - StaticInvoke(classOf[Math], + Catalyst.staticInvoke(classOf[Math], IntegerType, "min", Literal(decimalType.scale) :: Invoke(Invoke(inputObject, "getValue", ObjectType(classOf[java.math.BigDecimal])), diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/IdCustomCoder.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/IdCustomCoder.scala index 899ff27f2c..cb1b3e85f8 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/IdCustomCoder.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/IdCustomCoder.scala @@ -24,7 +24,7 @@ package au.csiro.pathling.encoders.datatypes import au.csiro.pathling.encoders.EncoderUtils.arrayExpression -import au.csiro.pathling.encoders.ExpressionWithName +import au.csiro.pathling.encoders.{Catalyst, ExpressionWithName} import org.apache.spark.sql.catalyst.expressions.Expression import org.apache.spark.sql.catalyst.expressions.objects.{Invoke, MapObjects, NewInstance, StaticInvoke} import org.apache.spark.sql.types._ @@ -72,11 +72,11 @@ case class IdCustomCoder(elementName: String) extends CustomCoder { override def customSerializer(evaluator: (Expression => Expression) => Expression): Seq[ExpressionWithName] = { val idExpression = evaluator( - exp => StaticInvoke(classOf[UTF8String], DataTypes.StringType, "fromString", + exp => Catalyst.staticInvoke(classOf[UTF8String], DataTypes.StringType, "fromString", List(Invoke(exp, "getIdPart", ObjectType(classOf[String]))))) val versionedIdExpression = evaluator( - exp => StaticInvoke(classOf[UTF8String], DataTypes.StringType, "fromString", + exp => Catalyst.staticInvoke(classOf[UTF8String], DataTypes.StringType, "fromString", List(Invoke(exp, "getValue", ObjectType(classOf[String]))))) Seq((elementName, idExpression), (versionedName, versionedIdExpression)) } diff --git a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/R4DataTypeMappings.scala b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/R4DataTypeMappings.scala index 920e1afad2..1bf67ed1a4 100644 --- a/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/R4DataTypeMappings.scala +++ b/encoders/src/main/scala/au/csiro/pathling/encoders/datatypes/R4DataTypeMappings.scala @@ -24,13 +24,12 @@ package au.csiro.pathling.encoders.datatypes import au.csiro.pathling.encoders.datatypes.R4DataTypeMappings.{fhirPrimitiveToSparkTypes, isValidOpenElementType} -import au.csiro.pathling.encoders.{ExpressionWithName, StaticField} +import au.csiro.pathling.encoders.{Catalyst, ExpressionWithName, StaticField} import ca.uhn.fhir.context._ import ca.uhn.fhir.model.api.TemporalPrecisionEnum import org.apache.spark.sql.catalyst.analysis.GetColumnByOrdinal import org.apache.spark.sql.catalyst.expressions.objects.{InitializeJavaBean, Invoke, NewInstance, StaticInvoke} import org.apache.spark.sql.catalyst.expressions.{Cast, Expression, Literal} -import org.apache.spark.sql.internal.SQLConf import org.apache.spark.sql.types.{DataType, DataTypes, ObjectType} import org.hl7.fhir.instance.model.api.{IBase, IBaseDatatype, IPrimitiveType} import org.hl7.fhir.r4.model._ @@ -55,36 +54,13 @@ class R4DataTypeMappings extends DataTypeMappings { override def baseType(): Class[_ <: IBaseDatatype] = classOf[org.hl7.fhir.r4.model.Type] - override def overrideCompositeExpression(inputObject: Expression, + override def overrideCompositeExpression(inputObject: Expression, definition: BaseRuntimeElementCompositeDefinition[_]): Option[Seq[ExpressionWithName]] = { - - if (definition.getImplementingClass == classOf[Reference]) { - // Reference type, so return only supported fields. We also explicitly use the IIDType for the - // reference element, since that differs from the conventions used to infer other types. - val reference = dataTypeToUtf8Expr( - Invoke(inputObject, - "getReferenceElement", - ObjectType(classOf[IdType]))) - - val display = dataTypeToUtf8Expr( - Invoke(inputObject, - "getDisplayElement", - ObjectType(classOf[org.hl7.fhir.r4.model.StringType]))) - - Some(List(("reference", reference), ("display", display))) - } else { - None - } + None } override def skipField(definition: BaseRuntimeElementCompositeDefinition[_], child: BaseRuntimeChildDefinition): Boolean = { - - // References may be recursive, so include only the reference adn display name. - val skipRecursiveReference = definition.getImplementingClass == classOf[Reference] && - !(child.getElementName == "reference" || - child.getElementName == "display") - // Contains elements are currently not encoded in our Spark dataset. val skipContains = definition .getImplementingClass == classOf[ValueSet.ValueSetExpansionContainsComponent] && @@ -95,8 +71,7 @@ class R4DataTypeMappings extends DataTypeMappings { // "modifierExtensionExtension", not "extensionExtension". // See: https://github.com/hapifhir/hapi-fhir/issues/3414 val skipModifierExtension = child.getElementName.equals("modifierExtension") - - skipRecursiveReference || skipContains || skipModifierExtension + skipContains || skipModifierExtension } override def primitiveEncoderExpression(inputObject: Expression, @@ -175,13 +150,13 @@ class R4DataTypeMappings extends DataTypeMappings { ObjectType(classOf[TemporalPrecisionEnum]), "MILLI") - val UTCZone = StaticInvoke(classOf[TimeZone], + val UTCZone = Catalyst.staticInvoke(classOf[TimeZone], ObjectType(classOf[TimeZone]), "getTimeZone", Literal("UTC", ObjectType(classOf[String])) :: Nil) NewInstance(primitiveClass, - List(StaticInvoke(org.apache.spark.sql.catalyst.util.DateTimeUtils.getClass, + List(Catalyst.staticInvoke(org.apache.spark.sql.catalyst.util.DateTimeUtils.getClass, ObjectType(classOf[java.sql.Timestamp]), "toJavaTimestamp", getPath :: Nil), diff --git a/encoders/src/main/scala/au/csiro/pathling/sql/types/FlexiDecimalSupport.scala b/encoders/src/main/scala/au/csiro/pathling/sql/types/FlexiDecimalSupport.scala index d4cfebdc7e..808ed5c99b 100644 --- a/encoders/src/main/scala/au/csiro/pathling/sql/types/FlexiDecimalSupport.scala +++ b/encoders/src/main/scala/au/csiro/pathling/sql/types/FlexiDecimalSupport.scala @@ -23,6 +23,7 @@ package au.csiro.pathling.sql.types +import au.csiro.pathling.encoders.Catalyst import org.apache.spark.sql.Column import org.apache.spark.sql.catalyst.expressions.objects.{Invoke, StaticInvoke} import org.apache.spark.sql.catalyst.expressions.{CreateNamedStruct, Expression, If, IsNull, Literal} @@ -57,12 +58,12 @@ object FlexiDecimalSupport { // TODO: Performance: consider if the normalized value could be cached in // a variable - val normalizedExpression: Expression = StaticInvoke(classOf[FlexiDecimal], + val normalizedExpression: Expression = Catalyst.staticInvoke(classOf[FlexiDecimal], ObjectType(classOf[java.math.BigDecimal]), "normalize", expression :: Nil) - val valueExpression: Expression = StaticInvoke(classOf[Decimal], + val valueExpression: Expression = Catalyst.staticInvoke(classOf[Decimal], FlexiDecimal.DECIMAL_TYPE, "apply", Invoke(normalizedExpression, "unscaledValue", diff --git a/encoders/src/test/java/au/csiro/pathling/encoders/FhirEncodersTest.java b/encoders/src/test/java/au/csiro/pathling/encoders/FhirEncodersTest.java index 426f55801f..b72a5a7793 100644 --- a/encoders/src/test/java/au/csiro/pathling/encoders/FhirEncodersTest.java +++ b/encoders/src/test/java/au/csiro/pathling/encoders/FhirEncodersTest.java @@ -23,11 +23,14 @@ package au.csiro.pathling.encoders; +import static org.apache.spark.sql.functions.col; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; +import ca.uhn.fhir.parser.IParser; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.math.BigDecimal; @@ -273,13 +276,80 @@ public void coding() { @Test public void reference() { + final Condition conditionWithReferences = TestData.conditionWithReferencesWithIdentifiers(); + + final Dataset conditionL3Dataset = spark + .createDataset(ImmutableList.of(conditionWithReferences), ENCODERS_L3.of(Condition.class)); + + final Condition decodedL3Condition = conditionL3Dataset.head(); + + assertEquals( + RowFactory.create( + "withReferencesWithIdentifiers", + "Patient/example", + "http://terminology.hl7.org/CodeSystem/v2-0203", + "MR", + "https://fhir.example.com/identifiers/mrn", + "urn:id" + ), + conditionL3Dataset.select( + col("id"), + col("subject.reference"), + col("subject.identifier.type.coding.system").getItem(0), + col("subject.identifier.type.coding.code").getItem(0), + col("subject.identifier.system"), + col("subject.identifier.value") + ).head()); + + assertEquals("Patient/example", + decodedL3Condition.getSubject().getReference()); + + assertEquals("urn:id", + decodedL3Condition.getSubject().getIdentifier().getValue()); + + // the assigner should be pruned from the reference identifier. + assertTrue(conditionWithReferences.getSubject().getIdentifier().hasAssigner()); + assertFalse(decodedL3Condition.getSubject().getIdentifier().hasAssigner()); + } + - assertEquals(condition.getSubject().getReference(), - conditionsDataset.select("subject.reference").head().get(0)); - assertEquals(condition.getSubject().getReference(), - decodedCondition.getSubject().getReference()); + @Test + public void identifier() { + final Condition conditionWithIdentifiers = TestData.conditionWithIdentifiersWithReferences(); + + final Dataset conditionL3Dataset = spark + .createDataset(ImmutableList.of(conditionWithIdentifiers), ENCODERS_L3.of(Condition.class)); + + final Condition decodedL3Condition = conditionL3Dataset.head(); + + assertEquals( + RowFactory.create( + "withIdentifiersWithReferences", + "http://terminology.hl7.org/CodeSystem/v2-0203", + "MR", + "https://fhir.example.com/identifiers/mrn", + "urn:id01", + "Organization/001", + "urn:id02" + ), + conditionL3Dataset.select( + col("id"), + col("identifier.type.coding").getItem(0).getField("system").getItem(0), + col("identifier.type.coding").getItem(0).getField("code").getItem(0), + col("identifier.system").getItem(0), + col("identifier.value").getItem(0), + col("identifier.assigner.reference").getItem(0), + col("identifier.assigner.identifier.value").getItem(0) + ).head()); + + // the assigner should be pruned from the reference identifier. + assertTrue(conditionWithIdentifiers.getIdentifier().get(0).getAssigner().getIdentifier() + .hasAssigner()); + assertFalse( + decodedL3Condition.getIdentifier().get(0).getAssigner().getIdentifier().hasAssigner()); } + @Test public void integer() { @@ -325,13 +395,13 @@ public void choiceBigDecimalInQuestionnaire() { .getAnswerDecimalType().getValue(); final BigDecimal queriedDecimal = (BigDecimal) questionnaireDataset - .select(functions.col("item").getItem(0).getField("enableWhen").getItem(0) + .select(col("item").getItem(0).getField("enableWhen").getItem(0) .getField("answerDecimal")) .head() .get(0); final int queriedDecimal_scale = questionnaireDataset - .select(functions.col("item").getItem(0).getField("enableWhen").getItem(0) + .select(col("item").getItem(0).getField("enableWhen").getItem(0) .getField("answerDecimal_scale")) .head() .getInt(0); @@ -360,13 +430,13 @@ public void choiceBigDecimalInQuestionnaireResponse() { .getValueDecimalType().getValue(); final BigDecimal queriedDecimal = (BigDecimal) questionnaireResponseDataset - .select(functions.col("item").getItem(0).getField("answer").getItem(0) + .select(col("item").getItem(0).getField("answer").getItem(0) .getField("valueDecimal")) .head() .get(0); final int queriedDecimal_scale = questionnaireResponseDataset - .select(functions.col("item").getItem(0).getField("answer").getItem(0) + .select(col("item").getItem(0).getField("answer").getItem(0) .getField("valueDecimal_scale")) .head() .getInt(0); @@ -516,13 +586,13 @@ public void testNestedQuestionnaire() { assertEquals(Stream.of("Item/0", "Item/0", "Item/0", "Item/0").map(RowFactory::create) .collect(Collectors.toUnmodifiableList()), - questionnaireDataset_L3.select(functions.col("item").getItem(0).getField("linkId")) + questionnaireDataset_L3.select(col("item").getItem(0).getField("linkId")) .collectAsList()); assertEquals(Stream.of(null, "Item/1.0", "Item/1.0", "Item/1.0").map(RowFactory::create) .collect(Collectors.toUnmodifiableList()), questionnaireDataset_L3 - .select(functions.col("item") + .select(col("item") .getItem(1).getField("item") .getItem(0).getField("linkId")) .collectAsList()); @@ -530,7 +600,7 @@ public void testNestedQuestionnaire() { assertEquals(Stream.of(null, null, "Item/2.1.0", "Item/2.1.0").map(RowFactory::create) .collect(Collectors.toUnmodifiableList()), questionnaireDataset_L3 - .select(functions.col("item") + .select(col("item") .getItem(2).getField("item") .getItem(1).getField("item") .getItem(0).getField("linkId")) @@ -539,7 +609,7 @@ public void testNestedQuestionnaire() { assertEquals(Stream.of(null, null, null, "Item/3.2.1.0").map(RowFactory::create) .collect(Collectors.toUnmodifiableList()), questionnaireDataset_L3 - .select(functions.col("item") + .select(col("item") .getItem(3).getField("item") .getItem(2).getField("item") .getItem(1).getField("item") @@ -555,4 +625,43 @@ public void testQuantityComparator() { assertEquals(originalComparator.toCode(), queriedComparator); } + + @Test + public void nullEncoding() { + // empty elements of all types should be encoded as nulls + final Observation emptyObservation = new Observation(); + assertFalse(emptyObservation.hasSubject()); + final Dataset observationsDataset = spark.createDataset( + ImmutableList.of(emptyObservation), + ENCODERS_L0.of(Observation.class)); + // 'subject' is a struct + // 'identifier' is an array of struct + // 'status' is a primitive type + final Row subjectRow = observationsDataset.toDF().select("subject", "identifier", "status") + .first(); + assertTrue(subjectRow.isNullAt(0)); + assertTrue(subjectRow.isNullAt(1)); + assertTrue(subjectRow.isNullAt(2)); + } + + + @Test + public void nullEncodingFromJson() { + final IParser parser = ENCODERS_L0.getContext().newJsonParser(); + final Observation emptyObservationFromJson = parser.parseResource(Observation.class, + "{ \"resourceType\": \"Observation\"}"); + assertFalse(emptyObservationFromJson.hasSubject()); + final Dataset observationsDataset = spark.createDataset( + ImmutableList.of(emptyObservationFromJson), + ENCODERS_L0.of(Observation.class)); + // 'subject' is a struct + // 'identifier' is an array of struct + // 'status' is a primitive type + final Row subjectRow = observationsDataset.toDF().select("subject", "identifier", "status") + .first(); + assertTrue(subjectRow.isNullAt(0)); + assertTrue(subjectRow.isNullAt(1)); + assertTrue(subjectRow.isNullAt(2)); + } + } diff --git a/encoders/src/test/java/au/csiro/pathling/encoders/LightweightFhirEncodersTest.java b/encoders/src/test/java/au/csiro/pathling/encoders/LightweightFhirEncodersTest.java index 849ded3fcd..6f1d11f512 100644 --- a/encoders/src/test/java/au/csiro/pathling/encoders/LightweightFhirEncodersTest.java +++ b/encoders/src/test/java/au/csiro/pathling/encoders/LightweightFhirEncodersTest.java @@ -41,16 +41,21 @@ import org.apache.spark.sql.Row; import org.apache.spark.sql.catalyst.InternalRow; import org.apache.spark.sql.catalyst.encoders.ExpressionEncoder; -import org.apache.spark.sql.catalyst.encoders.RowEncoder; import org.hl7.fhir.r4.model.BaseResource; +import org.hl7.fhir.r4.model.CodeableConcept; +import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Device; +import org.hl7.fhir.r4.model.Expression; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Identifier.IdentifierUse; import org.hl7.fhir.r4.model.MolecularSequence; import org.hl7.fhir.r4.model.MolecularSequence.MolecularSequenceQualityRocComponent; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.PlanDefinition; import org.hl7.fhir.r4.model.PlanDefinition.PlanDefinitionActionComponent; +import org.hl7.fhir.r4.model.Reference; import org.json4s.jackson.JsonMethods; import org.junit.jupiter.api.Test; import scala.collection.mutable.WrappedArray; @@ -162,6 +167,65 @@ public void testHtmlNarrative() { assertSerDeIsIdentity(encoder, conditionWithNarrative); } + @Test + public void testReference() { + final ExpressionEncoder encoder = fhirEncoders + .of(Condition.class); + final Condition conditionWithFullReference = new Condition(); + final Identifier identifier = new Identifier() + .setSystem("urn:id-system") + .setValue("id-valule") + .setUse(IdentifierUse.OFFICIAL) + .setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("system")) + .setText("text")); + final Reference referenceWithAllFields = new Reference("Patient/1234") + .setDisplay("Some Display Name") + .setType("Patient") + .setIdentifier(identifier); + // Set also the Element inherited fields + referenceWithAllFields.setId("some-id"); + conditionWithFullReference.setSubject(referenceWithAllFields); + assertSerDeIsIdentity(encoder, conditionWithFullReference); + } + + @Test + public void testIdentifier() { + final ExpressionEncoder encoder = fhirEncoders + .of(Condition.class); + final Condition conditionWithIdentifierWithAssigner = new Condition(); + + final Reference assignerReference = new Reference("Organization/1234") + .setDisplay("Some Display Name") + .setType("Organization"); + + final Identifier identifier = new Identifier() + .setSystem("urn:id-system") + .setValue("id-valule") + .setUse(IdentifierUse.OFFICIAL) + .setAssigner(assignerReference) + .setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("system")) + .setText("text")); + conditionWithIdentifierWithAssigner.addIdentifier(identifier); + assertSerDeIsIdentity(encoder, conditionWithIdentifierWithAssigner); + } + + @Test + public void testExpression() { + + // Expression contains 'reference' field + // We are checking that it is encoded in generic way not and not the subject to special case for Reference 'reference' field. + final ExpressionEncoder encoder = fhirEncoders + .of(PlanDefinition.class); + + final PlanDefinition planDefinition = new PlanDefinition(); + + final PlanDefinitionActionComponent actionComponent = planDefinition + .getActionFirstRep(); + actionComponent.getConditionFirstRep().setExpression(new Expression().setLanguage("language") + .setExpression("expression").setDescription("description")); + assertSerDeIsIdentity(encoder, planDefinition); + } + @Test public void testThrowsExceptionWhenUnsupportedResource() { for (final String resourceName : EXCLUDED_RESOURCES) { @@ -191,7 +255,7 @@ public void testEncodesExtensions() { // Deserialize the InternalRow to a Row with explicit schema. final ExpressionEncoder rowEncoder = EncoderUtils - .defaultResolveAndBind(RowEncoder.apply(encoder.schema())); + .defaultResolveAndBind(ExpressionEncoder.apply(encoder.schema())); final Row conditionRow = rowEncoder.createDeserializer().apply(serializedRow); // Get the extensionContainer. @@ -241,7 +305,7 @@ public void testQuantityCanonicalization() { final InternalRow serializedRow = resolvedEncoder.createSerializer().apply(observation); final ExpressionEncoder rowEncoder = EncoderUtils.defaultResolveAndBind( - RowEncoder.apply(encoder.schema())); + ExpressionEncoder.apply(encoder.schema())); final Row observationRow = rowEncoder.createDeserializer().apply(serializedRow); final Row quantityRow = observationRow.getStruct(observationRow.fieldIndex("valueQuantity")); @@ -258,13 +322,13 @@ public void testQuantityArrayCanonicalization() { final InternalRow serializedRow = resolvedEncoder.createSerializer().apply(device); final ExpressionEncoder rowEncoder = EncoderUtils.defaultResolveAndBind( - RowEncoder.apply(encoder.schema())); + ExpressionEncoder.apply(encoder.schema())); final Row deviceRow = rowEncoder.createDeserializer().apply(serializedRow); final List properties = deviceRow.getList(deviceRow.fieldIndex("property")); final Row propertyRow = properties.get(0); final List quantityArray = propertyRow.getList(propertyRow.fieldIndex("valueQuantity")); - + final Row quantity1 = quantityArray.get(0); assertQuantity(quantity1, "0.0010", "m"); diff --git a/encoders/src/test/java/au/csiro/pathling/encoders/SchemaConverterTest.java b/encoders/src/test/java/au/csiro/pathling/encoders/SchemaConverterTest.java index 02fc596650..eb36ee8467 100644 --- a/encoders/src/test/java/au/csiro/pathling/encoders/SchemaConverterTest.java +++ b/encoders/src/test/java/au/csiro/pathling/encoders/SchemaConverterTest.java @@ -25,6 +25,7 @@ import static au.csiro.pathling.test.SchemaAsserts.assertFieldNotPresent; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -93,9 +94,8 @@ public class SchemaConverterTest { private StructType medRequestSchema; private StructType questionnaireSchema; private StructType questionnaireResponseSchema; - private StructType deviceSchema; - + private StructType observationSchema_L2; /** * Traverses a DataType recursively passing all encountered StructTypes to the provided consumer. @@ -173,16 +173,17 @@ public void setUp() { questionnaireSchema = converter_L0.resourceSchema(Questionnaire.class); questionnaireResponseSchema = converter_L0.resourceSchema(QuestionnaireResponse.class); deviceSchema = converter_L0.resourceSchema(Device.class); + observationSchema_L2 = converter_L2.resourceSchema(Observation.class); } @Test public void resourceHasId() { - assertTrue(getField(conditionSchema, true, "id") instanceof StringType); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "id")); } @Test public void boundCodeToStruct() { - assertTrue(getField(conditionSchema, true, "verificationStatus") instanceof StructType); + assertInstanceOf(StructType.class, getField(conditionSchema, true, "verificationStatus")); } @Test @@ -190,11 +191,11 @@ public void codingToStruct() { final DataType codingType = getField(conditionSchema, true, "severity", "coding"); - assertTrue(getField(codingType, true, "system") instanceof StringType); - assertTrue(getField(codingType, true, "version") instanceof StringType); - assertTrue(getField(codingType, true, "code") instanceof StringType); - assertTrue(getField(codingType, true, "display") instanceof StringType); - assertTrue(getField(codingType, true, "userSelected") instanceof BooleanType); + assertInstanceOf(StringType.class, getField(codingType, true, "system")); + assertInstanceOf(StringType.class, getField(codingType, true, "version")); + assertInstanceOf(StringType.class, getField(codingType, true, "code")); + assertInstanceOf(StringType.class, getField(codingType, true, "display")); + assertInstanceOf(BooleanType.class, getField(codingType, true, "userSelected")); } @Test @@ -202,30 +203,30 @@ public void codeableConceptToStruct() { final DataType codeableType = getField(conditionSchema, true, "severity"); - assertTrue(codeableType instanceof StructType); - assertTrue(getField(codeableType, true, "coding") instanceof ArrayType); - assertTrue(getField(codeableType, true, "text") instanceof StringType); + assertInstanceOf(StructType.class, codeableType); + assertInstanceOf(ArrayType.class, getField(codeableType, true, "coding")); + assertInstanceOf(StringType.class, getField(codeableType, true, "text")); } @Test public void idToString() { - assertTrue(getField(conditionSchema, true, "id") instanceof StringType); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "id")); } @Test public void narrativeToStruct() { - assertTrue(getField(conditionSchema, true, "text", "status") instanceof StringType); - assertTrue(getField(conditionSchema, true, "text", "div") instanceof StringType); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "text", "status")); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "text", "div")); } @Test public void expandChoiceFields() { - assertTrue(getField(conditionSchema, true, "onsetPeriod") instanceof StructType); - assertTrue(getField(conditionSchema, true, "onsetRange") instanceof StructType); - assertTrue(getField(conditionSchema, true, "onsetDateTime") instanceof StringType); - assertTrue(getField(conditionSchema, true, "onsetString") instanceof StringType); - assertTrue(getField(conditionSchema, true, "onsetAge") instanceof StructType); + assertInstanceOf(StructType.class, getField(conditionSchema, true, "onsetPeriod")); + assertInstanceOf(StructType.class, getField(conditionSchema, true, "onsetRange")); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "onsetDateTime")); + assertInstanceOf(StringType.class, getField(conditionSchema, true, "onsetString")); + assertInstanceOf(StructType.class, getField(conditionSchema, true, "onsetAge")); } @Test @@ -244,19 +245,21 @@ public void orderChoiceFields() { @Test public void decimalWithinChoiceField() { - assertTrue(getField(questionnaireSchema, true, "item", "enableWhen", - "answerDecimal") instanceof DecimalType); - assertTrue(getField(questionnaireSchema, true, "item", "enableWhen", - "answerDecimal_scale") instanceof IntegerType); - assertTrue(getField(questionnaireResponseSchema, true, "item", "answer", - "valueDecimal") instanceof DecimalType); - assertTrue(getField(questionnaireResponseSchema, true, "item", "answer", - "valueDecimal_scale") instanceof IntegerType); + assertInstanceOf(DecimalType.class, getField(questionnaireSchema, true, "item", "enableWhen", + "answerDecimal")); + assertInstanceOf(IntegerType.class, getField(questionnaireSchema, true, "item", "enableWhen", + "answerDecimal_scale")); + assertInstanceOf(DecimalType.class, + getField(questionnaireResponseSchema, true, "item", "answer", + "valueDecimal")); + assertInstanceOf(IntegerType.class, + getField(questionnaireResponseSchema, true, "item", "answer", + "valueDecimal_scale")); } @Test public void instantToTimestamp() { - assertTrue(getField(observationSchema, true, "issued") instanceof TimestampType); + assertInstanceOf(TimestampType.class, getField(observationSchema, true, "issued")); } @Test @@ -266,17 +269,59 @@ public void timeToString() { @Test public void bigDecimalToDecimal() { - assertTrue( - getField(observationSchema, true, "valueQuantity", "value") instanceof DecimalType); + assertInstanceOf(DecimalType.class, + getField(observationSchema, true, "valueQuantity", "value")); } @Test public void reference() { - assertTrue( - getField(observationSchema, true, "subject", "reference") instanceof StringType); - assertTrue(getField(observationSchema, true, "subject", "display") instanceof StringType); + assertInstanceOf(StringType.class, getField(observationSchema, true, "subject", "id")); + assertInstanceOf(StringType.class, getField(observationSchema, true, "subject", "reference")); + assertInstanceOf(StringType.class, getField(observationSchema, true, "subject", "display")); + assertInstanceOf(StringType.class, getField(observationSchema, true, "subject", "type")); + assertInstanceOf(StructType.class, getField(observationSchema, true, "subject", "identifier")); + assertInstanceOf(StringType.class, + getField(observationSchema, true, "subject", "identifier", "value")); + } + @Test + public void identifier() { + assertInstanceOf(StringType.class, + unArray(getField(observationSchema, true, "identifier", "value"))); + // `assigner` field should be present in the root level `Identifier` schema. + assertInstanceOf(StructType.class, + unArray(getField(observationSchema, true, "identifier", "assigner"))); + assertInstanceOf(StringType.class, + unArray(getField(observationSchema, true, "identifier", "assigner", "reference"))); + + } + + @Test + public void identifierInReference() { + // + // Identifier (assigner) in root Reference + // + assertFieldNotPresent("assigner", getField(observationSchema, true, "subject", "identifier")); + // The `assigner` field should not be present in Identifier schema of the Reference `identifier` field. + assertFieldNotPresent("assigner", + getField(observationSchema_L2, true, "subject", "identifier")); + + // + // Identifier (assigner) in a Reference nested in an Identifier + // + // the `identifier` field should not be present because for normal nesting rules for 0-level nesting + assertFieldNotPresent("identifier", + unArray(getField(observationSchema, true, "identifier", "assigner"))); + // the `identifier` field should be present because for normal nesting rules for 2-level nesting + assertInstanceOf(StructType.class, + unArray(getField(observationSchema_L2, true, "identifier", "assigner", "identifier"))); + // but it should not have the assigner field + assertFieldNotPresent("assigner", + unArray(getField(observationSchema_L2, true, "identifier", "assigner", "identifier"))); + } + + @Test public void preferredNameOnly() { @@ -374,7 +419,7 @@ public void testExtensions() { final MapType extensionsContainerType = (MapType) getField(extensionSchema, true, "_extension"); assertEquals(DataTypes.IntegerType, extensionsContainerType.keyType()); - assertTrue(extensionsContainerType.valueType() instanceof ArrayType); + assertInstanceOf(ArrayType.class, extensionsContainerType.valueType()); traverseSchema(extensionSchema, t -> { assertEquals(DataTypes.IntegerType, t.fields()[t.fieldIndex("_fid")].dataType()); @@ -424,13 +469,13 @@ public void testQuantityArray() { } private void assertQuantityType(final DataType quantityType) { - assertTrue(getField(quantityType, true, "value") instanceof DecimalType); - assertTrue(getField(quantityType, true, "value_scale") instanceof IntegerType); - assertTrue(getField(quantityType, true, "comparator") instanceof StringType); - assertTrue(getField(quantityType, true, "unit") instanceof StringType); - assertTrue(getField(quantityType, true, "system") instanceof StringType); - assertTrue(getField(quantityType, true, "code") instanceof StringType); + assertInstanceOf(DecimalType.class, getField(quantityType, true, "value")); + assertInstanceOf(IntegerType.class, getField(quantityType, true, "value_scale")); + assertInstanceOf(StringType.class, getField(quantityType, true, "comparator")); + assertInstanceOf(StringType.class, getField(quantityType, true, "unit")); + assertInstanceOf(StringType.class, getField(quantityType, true, "system")); + assertInstanceOf(StringType.class, getField(quantityType, true, "code")); assertEquals(FlexiDecimal.DATA_TYPE, getField(quantityType, true, "_value_canonicalized")); - assertTrue(getField(quantityType, true, "_code_canonicalized") instanceof StringType); + assertInstanceOf(StringType.class, getField(quantityType, true, "_code_canonicalized")); } } diff --git a/encoders/src/test/java/au/csiro/pathling/encoders/TestData.java b/encoders/src/test/java/au/csiro/pathling/encoders/TestData.java index 010d1586a3..1faa47c94b 100644 --- a/encoders/src/test/java/au/csiro/pathling/encoders/TestData.java +++ b/encoders/src/test/java/au/csiro/pathling/encoders/TestData.java @@ -138,6 +138,44 @@ public static Condition newCondition() { return condition; } + public static Condition conditionWithReferencesWithIdentifiers() { + final Condition condition = new Condition(); + condition.setId("withReferencesWithIdentifiers"); + final Coding typeCoding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "MR", + null); + final CodeableConcept typeConcept = new CodeableConcept(typeCoding); + condition.setSubject( + new Reference("Patient/example") + .setDisplay("Display name") + .setIdentifier( + new Identifier() + .setType(typeConcept) + .setSystem("https://fhir.example.com/identifiers/mrn") + .setValue("urn:id") + .setAssigner(new Reference("Organization/001")) + ) + ); + return condition; + } + + public static Condition conditionWithIdentifiersWithReferences() { + final Condition condition = new Condition(); + condition.setId("withIdentifiersWithReferences"); + final Coding typeCoding = new Coding("http://terminology.hl7.org/CodeSystem/v2-0203", "MR", + null); + final CodeableConcept typeConcept = new CodeableConcept(typeCoding); + condition + .addIdentifier() + .setType(typeConcept) + .setSystem("https://fhir.example.com/identifiers/mrn") + .setValue("urn:id01") + .setAssigner(new Reference("Organization/001") + .setIdentifier(new Identifier().setValue("urn:id02") + .setAssigner(new Reference("Organization/002")))); + return condition; + } + + public static Condition conditionWithVersion() { final Condition condition = new Condition(); final IdType id = new IdType("Condition", "with-version", "1"); @@ -145,6 +183,7 @@ public static Condition conditionWithVersion() { return condition; } + /** * Returns a FHIR Observation for testing purposes. */ diff --git a/encoders/src/test/java/au/csiro/pathling/sql/types/FlexiDecimalTest.java b/encoders/src/test/java/au/csiro/pathling/sql/types/FlexiDecimalTest.java index 48026f7a62..ad9abda91b 100644 --- a/encoders/src/test/java/au/csiro/pathling/sql/types/FlexiDecimalTest.java +++ b/encoders/src/test/java/au/csiro/pathling/sql/types/FlexiDecimalTest.java @@ -23,12 +23,12 @@ package au.csiro.pathling.sql.types; -import org.junit.jupiter.api.Test; -import java.math.BigDecimal; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import java.math.BigDecimal; +import org.junit.jupiter.api.Test; + public class FlexiDecimalTest { @Test diff --git a/encoders/src/test/resources/logback-test.xml b/encoders/src/test/resources/logback.xml similarity index 87% rename from encoders/src/test/resources/logback-test.xml rename to encoders/src/test/resources/logback.xml index 13d2caaf38..76d5575d86 100644 --- a/encoders/src/test/resources/logback-test.xml +++ b/encoders/src/test/resources/logback.xml @@ -25,7 +25,7 @@ - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + [%level] %logger{36} - %msg%n @@ -36,12 +36,14 @@ - + + + diff --git a/fhir-server/pom.xml b/fhir-server/pom.xml index 40da33e48a..dcc3d3c554 100644 --- a/fhir-server/pom.xml +++ b/fhir-server/pom.xml @@ -36,8 +36,12 @@ aehrc/pathling latest arm64 - 1.36 - amazoncorretto:11 + 1.37 + amazoncorretto:17 + file://${project.basedir}/src/test/resources/test-data + parquet + 1 + 5 @@ -65,7 +69,7 @@ io.delta - delta-core_${pathling.scalaVersion} + delta-spark_${pathling.scalaVersion} org.apache.hadoop @@ -75,6 +79,10 @@ com.fasterxml.jackson.core jackson-core + + javax.servlet + javax.servlet-api + @@ -125,6 +133,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.boot + spring-boot-starter-jetty + org.springframework.boot spring-boot-starter-actuator @@ -153,7 +165,7 @@ com.auth0 jwks-rsa - 0.22.0 + 0.22.1 com.google.code.gson @@ -178,7 +190,7 @@ io.micrometer micrometer-registry-prometheus - 1.11.3 + 1.13.0 @@ -355,7 +367,7 @@ com.google.cloud.tools jib-maven-plugin - 3.3.2 + 3.4.2 ${pathling.dockerBaseImage} @@ -378,7 +390,8 @@ ${project.version} - -Xmx2g -XX:MaxMetaspaceSize=400m -XX:ReservedCodeCacheSize=240m -Xss1m -Duser.timezone=UTC + + -Xmx2g -XX:MaxMetaspaceSize=400m -XX:ReservedCodeCacheSize=240m -Xss1m -Duser.timezone=UTC --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED Copyright © 2018-2023, Commonwealth Scientific and Industrial Research Organisation (CSIRO) ABN 41 687 119 230. Licensed under the CSIRO Open Source Software Licence Agreement. @@ -408,6 +421,13 @@ + + + maven_central + Maven Central + https://repo.maven.apache.org/maven2/ + + @@ -422,29 +442,29 @@ run-benchmark test - java + exec - au.csiro.pathling.test.benchmark.PathlingBenchmarkRunner - test - false - - - spring.profiles.active - unit-test - - - pathling.storage.warehouseUrl - file://${project.basedir}/src/test/resources/test-data - - - pathling.storage.databaseName - parquet - - + java + -classpath + + -Xmx6g + -XX:MaxMetaspaceSize=400m + -XX:ReservedCodeCacheSize=240m + -Xss1m + -Duser.timezone=UTC + --add-exports=java.base/sun.nio.ch=ALL-UNNAMED + --add-opens=java.base/java.net=ALL-UNNAMED + -Dspring.profiles.active=unit-test + -Dpathling.storage.warehouseUrl=${pathling.benchmark.warehouseUrl} + -Dpathling.storage.databaseName=${pathling.benchmark.databaseName} + -Dbenchmark.test.iterations=${pathling.benchmark.testIterations} + -Dbenchmark.warmup.iterations=${pathling.benchmark.warmupIterations} + au.csiro.pathling.test.benchmark.PathlingBenchmarkRunner ${project.build.directory}/benchmark + test diff --git a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java index a2aab3b5db..c654ca4c08 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/aggregate/AggregateExecutor.java @@ -120,8 +120,7 @@ private Function mapRowToGrouping( } for (int i = 0; i < aggregations.size(); i++) { - //noinspection rawtypes - final Materializable aggregation = (Materializable) aggregations.get(i); + final Materializable aggregation = (Materializable) aggregations.get(i); // Delegate to the `getValueFromRow` method within each Materializable path class to extract // the Type value from the Row in the appropriate way. final Optional result = aggregation.getFhirValueFromRow(row, i + groupings.size()); diff --git a/fhir-server/src/main/java/au/csiro/pathling/async/AsyncAspect.java b/fhir-server/src/main/java/au/csiro/pathling/async/AsyncAspect.java index 405b559c0a..26ce948d46 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/async/AsyncAspect.java +++ b/fhir-server/src/main/java/au/csiro/pathling/async/AsyncAspect.java @@ -25,6 +25,8 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import jakarta.annotation.Nonnull; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.UndeclaredThrowableException; import java.util.Arrays; import java.util.List; @@ -34,8 +36,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.stream.Collectors; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.SparkSession; import org.aspectj.lang.ProceedingJoinPoint; diff --git a/fhir-server/src/main/java/au/csiro/pathling/async/JobProvider.java b/fhir-server/src/main/java/au/csiro/pathling/async/JobProvider.java index 090477ca89..4c498759b1 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/async/JobProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/async/JobProvider.java @@ -33,11 +33,11 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.OperationOutcome; diff --git a/fhir-server/src/main/java/au/csiro/pathling/caching/EntityTagInterceptor.java b/fhir-server/src/main/java/au/csiro/pathling/caching/EntityTagInterceptor.java index c55a187fa6..cb3f7fc6ad 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/caching/EntityTagInterceptor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/caching/EntityTagInterceptor.java @@ -30,10 +30,10 @@ import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/AsyncConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/AsyncConfiguration.java index 88be9077ea..1a610d3cd3 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/AsyncConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/AsyncConfiguration.java @@ -17,9 +17,9 @@ package au.csiro.pathling.config; -import javax.validation.constraints.NotNull; -import lombok.Data; +import jakarta.validation.constraints.NotNull; import java.util.List; +import lombok.Data; /** * Represents configuration relating to asynchronous processing. @@ -32,7 +32,7 @@ public class AsyncConfiguration { */ @NotNull private boolean enabled; - + /** * List of headers from exclude from the {@link HttpServerCachingConfiguration#getVary()} list for * the purpose of server side caching of asynchronous requests. These are likely to include diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/AuthorizationConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/AuthorizationConfiguration.java index df47604062..1504e74065 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/AuthorizationConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/AuthorizationConfiguration.java @@ -19,9 +19,9 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Optional; -import javax.validation.constraints.NotNull; import lombok.Data; import lombok.ToString; diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/CorsConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/CorsConfiguration.java index e44f0ebf4f..a89c016852 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/CorsConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/CorsConfiguration.java @@ -17,9 +17,9 @@ package au.csiro.pathling.config; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; import java.util.List; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotNull; import lombok.Data; /** diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/HttpServerCachingConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/HttpServerCachingConfiguration.java index 22bf908400..411406dcf5 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/HttpServerCachingConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/HttpServerCachingConfiguration.java @@ -17,8 +17,8 @@ package au.csiro.pathling.config; +import jakarta.validation.constraints.NotNull; import java.util.List; -import javax.validation.constraints.NotNull; import lombok.Data; @Data diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/ImportConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/ImportConfiguration.java index 259c69def4..e534d85fcc 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/ImportConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/ImportConfiguration.java @@ -17,8 +17,8 @@ package au.csiro.pathling.config; +import jakarta.validation.constraints.NotNull; import java.util.List; -import javax.validation.constraints.NotNull; import lombok.Data; /** diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/ServerConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/ServerConfiguration.java index 5cfdfb5cde..6c078b69fd 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/ServerConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/ServerConfiguration.java @@ -19,8 +19,8 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; import java.util.Optional; -import javax.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.Data; import lombok.Getter; diff --git a/fhir-server/src/main/java/au/csiro/pathling/config/SparkConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/config/SparkConfiguration.java index 5511ec2caa..ae47090af9 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/config/SparkConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/config/SparkConfiguration.java @@ -17,8 +17,7 @@ package au.csiro.pathling.config; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; import lombok.Builder; import lombok.Data; diff --git a/fhir-server/src/main/java/au/csiro/pathling/errors/DiagnosticContext.java b/fhir-server/src/main/java/au/csiro/pathling/errors/DiagnosticContext.java index 5f27e79b90..44a2ea224f 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/errors/DiagnosticContext.java +++ b/fhir-server/src/main/java/au/csiro/pathling/errors/DiagnosticContext.java @@ -25,8 +25,12 @@ import io.sentry.protocol.Request; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; +import java.util.Objects; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.servlet.http.HttpServletRequest; import org.slf4j.MDC; diff --git a/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorHandlingInterceptor.java b/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorHandlingInterceptor.java index e024799627..0d1556db0c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorHandlingInterceptor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorHandlingInterceptor.java @@ -34,10 +34,10 @@ import com.google.common.util.concurrent.UncheckedExecutionException; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.spark.SparkException; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.OperationOutcome.IssueSeverity; diff --git a/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorReportingInterceptor.java b/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorReportingInterceptor.java index 6abf3d1860..30e85ebcba 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorReportingInterceptor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/errors/ErrorReportingInterceptor.java @@ -26,8 +26,8 @@ import io.sentry.Sentry; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; diff --git a/fhir-server/src/main/java/au/csiro/pathling/extract/ResultProvider.java b/fhir-server/src/main/java/au/csiro/pathling/extract/ResultProvider.java index cee45882a4..4ab9d2cfcf 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/extract/ResultProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/extract/ResultProvider.java @@ -29,11 +29,11 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.util.Optional; import java.util.regex.Pattern; -import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.springframework.context.annotation.Profile; diff --git a/fhir-server/src/main/java/au/csiro/pathling/fhir/ConformanceProvider.java b/fhir-server/src/main/java/au/csiro/pathling/fhir/ConformanceProvider.java index 5928000462..1c5c81b031 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/fhir/ConformanceProvider.java +++ b/fhir-server/src/main/java/au/csiro/pathling/fhir/ConformanceProvider.java @@ -41,6 +41,8 @@ import com.google.common.collect.ImmutableMap; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; @@ -49,6 +51,8 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; diff --git a/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java b/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java index 8cc5b948d6..db4e6eb041 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java +++ b/fhir-server/src/main/java/au/csiro/pathling/fhir/FhirServer.java @@ -39,6 +39,9 @@ import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor; import jakarta.annotation.Nonnull; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServletResponse; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -46,9 +49,6 @@ import java.util.List; import java.util.Optional; import java.util.Set; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Enumerations; diff --git a/fhir-server/src/main/java/au/csiro/pathling/fhir/SmartConfigurationInterceptor.java b/fhir-server/src/main/java/au/csiro/pathling/fhir/SmartConfigurationInterceptor.java index 2812560ee7..a134497f4e 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/fhir/SmartConfigurationInterceptor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/fhir/SmartConfigurationInterceptor.java @@ -30,12 +30,12 @@ import com.google.gson.GsonBuilder; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import lombok.Setter; import lombok.extern.slf4j.Slf4j; diff --git a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java index 00a6038a38..a3ac80de3e 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java +++ b/fhir-server/src/main/java/au/csiro/pathling/search/SearchExecutor.java @@ -35,6 +35,8 @@ import ca.uhn.fhir.rest.param.StringParam; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import java.util.Date; import java.util.List; import java.util.Optional; diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/OidcConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/security/OidcConfiguration.java index 7e8349637f..0e6588aed0 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/OidcConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/OidcConfiguration.java @@ -69,6 +69,15 @@ public OidcConfiguration(@Nonnull final String issuer) { oidcConfiguration = getConfigurationForIssuerLocation(issuer); } + /** + * This constructor is used for testing purposes only. + * + * @param oidcConfiguration a map of OIDC {@link ConfigItem} values + */ + public OidcConfiguration(@Nonnull final Map oidcConfiguration) { + this.oidcConfiguration = oidcConfiguration; + } + /** * @param item the {@link ConfigItem} to retrieve * @return the value, if present diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/PathlingJwtDecoderBuilder.java b/fhir-server/src/main/java/au/csiro/pathling/security/PathlingJwtDecoderBuilder.java index 911ba83a0d..41428b516c 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/PathlingJwtDecoderBuilder.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/PathlingJwtDecoderBuilder.java @@ -53,6 +53,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -133,10 +134,7 @@ public List selectKeys(@Nullable final JWSHeader header, @Nonnull protected NimbusJwtDecoder buildDecoderWithValidators( @Nonnull final List> validators) { - final OAuth2TokenValidator[] validatorsArray = validators.toArray(new OAuth2TokenValidator[0]); - @SuppressWarnings("unchecked") - final OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>( - validatorsArray); + final OAuth2TokenValidator validator = new DelegatingOAuth2TokenValidator<>(validators); final NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(processor()); jwtDecoder.setJwtValidator(validator); @@ -172,7 +170,7 @@ public Resource retrieveResource(@Nullable final URL url) throws IOException { final HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON, APPLICATION_JWK_SET_JSON)); final ResponseEntity response = getResponse(url, headers); - if (response.getStatusCodeValue() != 200) { + if (!HttpStatusCode.valueOf(200).equals(response.getStatusCode())) { throw new IOException(response.toString()); } if (response.getBody() == null) { diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/SecurityConfiguration.java b/fhir-server/src/main/java/au/csiro/pathling/security/SecurityConfiguration.java index 92fa8573f1..d2c7404497 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/SecurityConfiguration.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/SecurityConfiguration.java @@ -17,15 +17,22 @@ package au.csiro.pathling.security; +import static au.csiro.pathling.utilities.Preconditions.check; + import au.csiro.pathling.config.ServerConfiguration; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -41,46 +48,73 @@ * href="https://stackoverflow.com/questions/51079564/spring-security-antmatchers-not-being-applied-on-post-requests-and-only-works-wi/51088555">Spring * security antMatchers not being applied on POST requests and only works with GET */ +@Configuration @EnableWebSecurity @Profile("server") @Slf4j public class SecurityConfiguration { + @Nonnull private final ServerConfiguration configuration; + @Nullable + private final JwtAuthenticationConverter authenticationConverter; + + @Nullable + private final JwtDecoder jwtDecoder; + @Value("${pathling.auth.enabled}") private boolean authEnabled; - public SecurityConfiguration(@Nonnull final ServerConfiguration configuration) { + /** + * Constructs a new {@link SecurityConfiguration} object. + * + * @param configuration a {@link ServerConfiguration} object + */ + public SecurityConfiguration(@Nonnull final ServerConfiguration configuration, + @Nullable final JwtAuthenticationConverter authenticationConverter, + @Nullable final JwtDecoder jwtDecoder) { this.configuration = configuration; + this.authenticationConverter = authenticationConverter; + this.jwtDecoder = jwtDecoder; } + /** + * Configures the security filter chain. + * + * @param http the {@link HttpSecurity} object + * @return the security filter chain + * @throws Exception if an error occurs + */ @Bean public SecurityFilterChain securityFilterChain(@Nonnull final HttpSecurity http) throws Exception { - // Will use the bean of class CorsConfigurationSource as configuration provider. - http.cors(); - if (authEnabled) { - http.authorizeRequests() - // The following requests do not require authentication. - .mvcMatchers(HttpMethod.GET, - "/metadata", // Server capabilities operation - "/OperationDefinition/**", // GET on OperationDefinition resources - "/.well-known/**") // SMART configuration endpoint - .permitAll() - // Anything else needs to be authenticated. - .anyRequest() - .authenticated() - .and() - .oauth2ResourceServer() - .jwt(); + check(authenticationConverter != null, + "Authentication converter must be provided when authentication is enabled"); + check(jwtDecoder != null, "JWT decoder must be provided when authentication is enabled"); + http.authorizeHttpRequests(authz -> authz + // The following requests do not require authentication. + .requestMatchers(HttpMethod.GET, "/fhir/metadata").permitAll() + .requestMatchers(HttpMethod.GET, "/fhir/OperationDefinition/**").permitAll() + .requestMatchers(HttpMethod.GET, "/fhir/.well-known/**").permitAll() + // Anything else needs to be authenticated. + .anyRequest().authenticated()) + // Enable CORS as per the configuration. + .cors((cors) -> cors.configurationSource(corsConfigurationSource())) + // Use the provided JWT decoder and authentication converter. + .oauth2ResourceServer(oauth2 -> oauth2 + .jwt((jwt) -> jwt + .jwtAuthenticationConverter(authenticationConverter) + .decoder(jwtDecoder) + )); } else { - http + http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()) + // Enable CORS as per the configuration. + .cors((cors) -> cors.configurationSource(corsConfigurationSource())) // Without this POST requests fail with 403 Forbidden. - .csrf().disable() - .authorizeRequests().anyRequest().permitAll(); + .csrf(AbstractHttpConfigurer::disable); } return http.build(); @@ -91,7 +125,6 @@ public SecurityFilterChain securityFilterChain(@Nonnull final HttpSecurity http) * * @return CORS configuration source */ - @Bean public CorsConfigurationSource corsConfigurationSource() { final CorsConfiguration cors = new CorsConfiguration(); cors.setAllowedOrigins(configuration.getCors().getAllowedOrigins()); diff --git a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportAuthenticationConverter.java b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportAuthenticationConverter.java index bd81283507..32896ac0c6 100644 --- a/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportAuthenticationConverter.java +++ b/fhir-server/src/main/java/au/csiro/pathling/security/ga4gh/PassportAuthenticationConverter.java @@ -32,7 +32,6 @@ import java.util.Collection; import java.util.List; import lombok.extern.slf4j.Slf4j; -import org.apache.hadoop.shaded.com.nimbusds.jose.shaded.json.JSONObject; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; @@ -42,6 +41,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.hl7.fhir.r4.model.Enumerations.ResourceType; +import org.json.JSONObject; import org.springframework.context.annotation.Profile; import org.springframework.core.convert.converter.Converter; import org.springframework.security.core.GrantedAuthority; diff --git a/fhir-server/src/main/resources/application.yml b/fhir-server/src/main/resources/application.yml index 566e63490d..407933b126 100644 --- a/fhir-server/src/main/resources/application.yml +++ b/fhir-server/src/main/resources/application.yml @@ -133,7 +133,7 @@ pathling: # This section configures the server's support asynchronous processing of HTTP requests. async: # Enables asynchronous processing for those operations that support it, - # when explicitly requested by a HTTP client. + # when explicitly requested by an HTTP client. enabled: true # A subset of `pathling.httpCaching.vary` HTTP headers, which should @@ -160,7 +160,7 @@ spark: delta: schema: autoMerge: - enabled: true + enabled: false scheduler: mode: FAIR diff --git a/library-api/src/main/resources/logback.xml b/fhir-server/src/main/resources/logback.xml similarity index 93% rename from library-api/src/main/resources/logback.xml rename to fhir-server/src/main/resources/logback.xml index cfd4dbdf57..273f9c4afc 100644 --- a/library-api/src/main/resources/logback.xml +++ b/fhir-server/src/main/resources/logback.xml @@ -24,6 +24,7 @@ + diff --git a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java index 7dfe02b07d..0832daa4b2 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/aggregate/DrillDownBuilderTest.java @@ -21,6 +21,7 @@ import au.csiro.pathling.test.SpringBootUnitTest; import au.csiro.pathling.test.helpers.TestHelpers; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.util.stream.Stream; import lombok.Value; import org.apache.spark.sql.SparkSession; diff --git a/fhir-server/src/test/java/au/csiro/pathling/async/JobRegistryTest.java b/fhir-server/src/test/java/au/csiro/pathling/async/JobRegistryTest.java index 381eef77a8..aa9991a013 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/async/JobRegistryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/async/JobRegistryTest.java @@ -30,7 +30,7 @@ class JobRegistryTest { - private final static Future MOCK_FUTURE = mock(Future.class); + private final static Future MOCK_FUTURE = mock(FutureResource.class); private final static JobTag JOB_TAG_1 = new JobTag() { }; private final static JobTag JOB_TAG_2 = new JobTag() { @@ -49,7 +49,6 @@ void testNewJobCanBeRetrievedById() { @Test void testReusesJobWhenTagsAreIdentical() { - assertEquals(JOB_TAG_1, JOB_TAG_1); final Job firstJob = registry.getOrCreate(JOB_TAG_1, id -> new Job(id, "operation", MOCK_FUTURE, Optional.empty())); final Job otherJob = registry.getOrCreate(JOB_TAG_1, @@ -73,4 +72,8 @@ void testCreatesNewJobIfTagsDiffer() { assertNotEquals(firstJob, otherJob); assertNotEquals(firstJob.getId(), otherJob.getId()); } + + static interface FutureResource extends Future { + } + } diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhir/EntityTagInterceptorTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhir/EntityTagInterceptorTest.java index 6e11170ee7..88a41527a5 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhir/EntityTagInterceptorTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhir/EntityTagInterceptorTest.java @@ -33,10 +33,10 @@ import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.util.List; import java.util.Optional; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; diff --git a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java index 0bbbc42bdf..208948c8b9 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/fhirpath/parser/ParserTest.java @@ -83,9 +83,9 @@ private void setupMockDisplayFor_195662009_444814009() { private void setupMockPropertiesFor_195662009_444814009() { TerminologyServiceHelpers.setupLookup(terminologyService) - .withProperty(CD_SNOMED_195662009, "child", (String) null, CD_SNOMED_40055000, - CD_SNOMED_403190006) - .withProperty(CD_SNOMED_444814009, "child", (String) null, CD_SNOMED_284551006) + .withProperty(CD_SNOMED_195662009, "child", null, List.of(CD_SNOMED_40055000, + CD_SNOMED_403190006)) + .withProperty(CD_SNOMED_444814009, "child", null, List.of(CD_SNOMED_284551006)) .withDesignation(CD_SNOMED_195662009, CD_SNOMED_900000000000003001, "en", "Acute viral pharyngitis : disorder") .withDesignation(CD_SNOMED_444814009, CD_SNOMED_900000000000003001, "en", @@ -905,14 +905,6 @@ void testQuantityAdditionWithOverflow() { .hasRows(spark, "responses/ParserTest/testQuantityAdditionWithOverflow_code.csv"); } - @Test - void testTraversalToUnsupportedReferenceChild() { - final String expression = "reverseResolve(MedicationRequest.subject).requester.identifier"; - final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, - expression); - assertEquals("No such child: " + expression, error.getMessage()); - } - @Test void testResolutionOfExtensionReference() { mockResource(ResourceType.PATIENT, ResourceType.ENCOUNTER, ResourceType.GOAL); diff --git a/fhir-server/src/test/java/au/csiro/pathling/jmh/AbstractJmhSpringBootState.java b/fhir-server/src/test/java/au/csiro/pathling/jmh/AbstractJmhSpringBootState.java index 7c96b4ef25..7f937e8496 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/jmh/AbstractJmhSpringBootState.java +++ b/fhir-server/src/test/java/au/csiro/pathling/jmh/AbstractJmhSpringBootState.java @@ -19,9 +19,10 @@ import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.test.context.ActiveProfiles; -import org.openjdk.jmh.annotations.State; /** @@ -36,7 +37,7 @@ * #wireUp()} comes up first which is now achieved by placing this class in 'au.csiro.pathling.jmh' * package and all the classes that use it in 'au.csiro.pathling.test.benchmark'. */ -@SpringBootTest +@SpringBootTest(webEnvironment = WebEnvironment.NONE) public abstract class AbstractJmhSpringBootState { @Setup(Level.Trial) diff --git a/fhir-server/src/test/java/au/csiro/pathling/jmh/SpringBootJmhContext.java b/fhir-server/src/test/java/au/csiro/pathling/jmh/SpringBootJmhContext.java index 9fc0836359..323b9a73fa 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/jmh/SpringBootJmhContext.java +++ b/fhir-server/src/test/java/au/csiro/pathling/jmh/SpringBootJmhContext.java @@ -21,6 +21,8 @@ import java.util.HashMap; import java.util.Map; import org.springframework.test.context.TestContextManager; +import org.springframework.test.annotation.DirtiesContext.HierarchyMode; +import org.springframework.test.context.TestContextManager; /** * This class hijacks the mechanism that SpringBoot uses for auto wiring of SpringBootTests. @@ -52,4 +54,17 @@ public static void autowireWithTestContext(@Nonnull final Object testLikeObject) throws Exception { getOrCreate(testLikeObject.getClass()).prepareTestInstance(testLikeObject); } + + + /** + * Cleans up the contexts of all the classes that have been autowired using this class. + * This also destroys and clean up all the test application contexts created thus far. + */ + public static void cleanUpAll() { + synchronized (contextManagers) { + contextManagers.forEach((k, v) -> v.getTestContext().markApplicationContextDirty( + HierarchyMode.EXHAUSTIVE)); + contextManagers.clear(); + } + } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/security/SecurityEnabledOperationsTest.java b/fhir-server/src/test/java/au/csiro/pathling/security/SecurityEnabledOperationsTest.java index 3cb0f009a0..687beb2ab2 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/security/SecurityEnabledOperationsTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/security/SecurityEnabledOperationsTest.java @@ -32,7 +32,8 @@ * href="https://stackoverflow.com/questions/58289509/in-spring-boot-test-how-do-i-map-a-temporary-folder-to-a-configuration-property">In * Spring Boot Test, how do I map a temporary folder to a configuration property? */ -@TestPropertySource(properties = {"pathling.auth.enabled=true"}) +@TestPropertySource(properties = {"pathling.auth.enabled=true", + "pathling.auth.issuer=https://pathling.acme.com/fhir"}) @MockBean(OidcConfiguration.class) @MockBean(JwtDecoder.class) @MockBean(JwtAuthenticationConverter.class) diff --git a/fhir-server/src/test/java/au/csiro/pathling/security/SecurityTest.java b/fhir-server/src/test/java/au/csiro/pathling/security/SecurityTest.java index 10b39af6dc..d9569675cc 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/security/SecurityTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/security/SecurityTest.java @@ -25,9 +25,10 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.function.Executable; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; @Tag("UnitTest") -@SpringBootTest +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) abstract class SecurityTest { static void assertThrowsAccessDenied(@Nonnull final Executable executable, diff --git a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java index 3c2d4edae3..3530d68eec 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/security/ga4gh/ManifestConverterTest.java @@ -41,6 +41,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.test.context.DynamicPropertyRegistry; @@ -52,7 +53,7 @@ "pathling.storage.databaseName=parquet"}) @Tag("Tranche2") @Slf4j -@SpringBootTest +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class ManifestConverterTest extends AbstractParserTest { @Autowired diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/IntegrationTestDependencies.java b/fhir-server/src/test/java/au/csiro/pathling/test/IntegrationTestDependencies.java index 7366af1463..0191551564 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/IntegrationTestDependencies.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/IntegrationTestDependencies.java @@ -18,14 +18,21 @@ package au.csiro.pathling.test; import au.csiro.pathling.config.ServerConfiguration; +import au.csiro.pathling.security.OidcConfiguration; +import au.csiro.pathling.security.OidcConfiguration.ConfigItem; import au.csiro.pathling.terminology.DefaultTerminologyServiceFactory; import ca.uhn.fhir.context.FhirContext; import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import jakarta.annotation.Nonnull; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -56,4 +63,10 @@ public DefaultTerminologyServiceFactory terminologyServiceFactory( return new DefaultTerminologyServiceFactory(fhirContext.getVersion().getVersion(), configuration.getTerminology()); } + + @Bean + public RestTemplateBuilder restTemplateBuilder() { + return new RestTemplateBuilder().setReadTimeout(Duration.ofSeconds(60)); + } + } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/TestDataImporter.java b/fhir-server/src/test/java/au/csiro/pathling/test/TestDataImporter.java index 356bbc8fc8..22389af2e5 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/TestDataImporter.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/TestDataImporter.java @@ -76,7 +76,7 @@ public void run(final String... args) { final String sourcePath = args[0]; final File srcNdJsonDir = new File(sourcePath); - final FileFilter fileFilter = new WildcardFileFilter("*.ndjson"); + final FileFilter fileFilter = WildcardFileFilter.builder().setWildcards("*.ndjson").get(); final File[] srcNdJsonFiles = srcNdJsonDir.listFiles(fileFilter); final List sources = Stream.of(requireNonNull(srcNdJsonFiles)) diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java index 8d1228e577..4d7c58aee8 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/AggregateBenchmark.java @@ -98,7 +98,7 @@ public void setUp() { SharedMocks.resetAll(); mockResource(ResourceType.PATIENT, ResourceType.CONDITION, ResourceType.ENCOUNTER, ResourceType.PROCEDURE, ResourceType.MEDICATIONREQUEST, ResourceType.OBSERVATION, - ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.QUESTIONNAIRE, + ResourceType.DIAGNOSTICREPORT, ResourceType.ORGANIZATION, ResourceType.CAREPLAN); executor = new AggregateExecutor(configuration, fhirContext, spark, database, diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/PathlingBenchmarkRunner.java b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/PathlingBenchmarkRunner.java index d83df4ef93..6ab38e0ff4 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/PathlingBenchmarkRunner.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/benchmark/PathlingBenchmarkRunner.java @@ -21,6 +21,7 @@ import java.io.File; import java.util.Properties; +import au.csiro.pathling.jmh.SpringBootJmhContext; import lombok.extern.slf4j.Slf4j; import org.openjdk.jmh.results.format.ResultFormatType; import org.openjdk.jmh.runner.Runner; @@ -42,7 +43,7 @@ public static void main(final String... argc) throws Exception { ? argc[0] : "target/benchmark"; - final Properties properties = PropertiesLoaderUtils.loadAllProperties("benchmark.properties"); + final Properties properties = System.getProperties(); final int warmup = Integer .parseInt(properties.getProperty("benchmark.warmup.iterations", "1")); @@ -79,7 +80,12 @@ public static void main(final String... argc) throws Exception { .shouldFailOnError(true) .jvmArgs("-server -Xmx4g -ea -Duser.timezone=UTC") .build(); - new Runner(opt).run(); + try { + new Runner(opt).run(); + } finally { + // clean up the test application contexts created by the benchmarks + SpringBootJmhContext.cleanUpAll(); + } } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java index a8fe7c9efa..35b36e2f2d 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AsyncTest.java @@ -35,12 +35,13 @@ import org.hl7.fhir.r4.model.Enumerations.ResourceType; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.sparkproject.jetty.http.HttpStatus; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.test.context.TestPropertySource; @@ -78,9 +79,9 @@ void asyncExtract() throws URISyntaxException, MalformedURLException, Interrupte log.info("Sending kick-off request"); final ResponseEntity response = restTemplate.exchange(uri, HttpMethod.GET, request, String.class); - assertEquals(HttpStatus.ACCEPTED_202, response.getStatusCode().value()); + assertEquals(HttpStatus.ACCEPTED, response.getStatusCode()); - assertAsyncResponse(response, HttpStatus.OK_200, true); + assertAsyncResponse(response, HttpStatus.OK, true); } @Test @@ -103,7 +104,7 @@ void error() throws URISyntaxException, MalformedURLException, InterruptedExcept .exchange(uri, HttpMethod.GET, request, String.class); assertTrue(response.getStatusCode().is2xxSuccessful()); - assertAsyncResponse(response, HttpStatus.BAD_REQUEST_400, false); + assertAsyncResponse(response, HttpStatus.BAD_REQUEST, false); } @Test @@ -111,13 +112,13 @@ void nonExistentJob() throws URISyntaxException { final String uri = "http://localhost:" + port + "/fhir/job?id=foo"; final ResponseEntity response = restTemplate .exchange(uri, HttpMethod.GET, RequestEntity.get(new URI(uri)).build(), String.class); - assertEquals(HttpStatus.NOT_FOUND_404, response.getStatusCode().value()); + assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } void assertAsyncResponse(@Nonnull final ResponseEntity response, - final int expectedStatus, final boolean inProgressRequired) + final HttpStatusCode expectedStatus, final boolean inProgressRequired) throws MalformedURLException, URISyntaxException, InterruptedException { - int statusCode; + HttpStatusCode statusCode; boolean encounteredInProgressResponse = false; final long startTime = new Date().getTime(); do { @@ -132,13 +133,13 @@ void assertAsyncResponse(@Nonnull final ResponseEntity response, final ResponseEntity statusResponse = restTemplate.exchange(statusUrl.toURI(), HttpMethod.GET, statusRequest, String.class); - statusCode = statusResponse.getStatusCodeValue(); + statusCode = statusResponse.getStatusCode(); final List eTag = statusResponse.getHeaders().get("ETag"); final List cacheControl = statusResponse.getHeaders().get("Cache-Control"); assertNotNull(eTag); assertNotNull(cacheControl); - if (statusCode != expectedStatus) { - assertEquals(HttpStatus.ACCEPTED_202, statusCode); + if (!expectedStatus.equals(statusCode)) { + assertEquals(HttpStatus.ACCEPTED, statusCode); assertTrue(eTag.contains("W/\"0\"")); assertTrue(cacheControl.contains("no-store")); encounteredInProgressResponse = true; @@ -158,7 +159,7 @@ void assertAsyncResponse(@Nonnull final ResponseEntity response, } @Nonnull - private String getContentLocation(@Nonnull ResponseEntity response) { + private String getContentLocation(@Nonnull final ResponseEntity response) { final List contentLocations = response.getHeaders().get("Content-Location"); assertNotNull(contentLocations); final String contentLocation = contentLocations.get(0); @@ -178,11 +179,11 @@ void identicalAsyncRequestsReturnTheSameJobId() log.info("Sending kick-off request"); final ResponseEntity response1 = restTemplate.exchange(uri, HttpMethod.GET, request, String.class); - assertEquals(HttpStatus.ACCEPTED_202, response1.getStatusCode().value()); + assertEquals(HttpStatus.ACCEPTED, response1.getStatusCode()); final ResponseEntity response2 = restTemplate.exchange(uri, HttpMethod.GET, request, String.class); - assertEquals(HttpStatus.ACCEPTED_202, response1.getStatusCode().value()); + assertEquals(HttpStatus.ACCEPTED, response1.getStatusCode()); assertEquals(getContentLocation(response1), getContentLocation(response2)); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AuthorizationConfigurationTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AuthorizationConfigurationTest.java index 976e001bbd..c35e119b36 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/AuthorizationConfigurationTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/AuthorizationConfigurationTest.java @@ -20,7 +20,6 @@ import static au.csiro.pathling.test.TestResources.assertJson; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.when; import au.csiro.pathling.security.OidcConfiguration; import au.csiro.pathling.security.OidcConfiguration.ConfigItem; @@ -29,19 +28,22 @@ import com.google.gson.GsonBuilder; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; -import java.util.Optional; +import java.util.Map; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.json.JSONException; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -74,30 +76,11 @@ class AuthorizationConfigurationTest extends IntegrationTest { TestRestTemplate restTemplate; @MockBean - OidcConfiguration oidcConfiguration; - - @MockBean - @SuppressWarnings("unused") JwtDecoder jwtDecoder; @MockBean - @SuppressWarnings("unused") JwtAuthenticationConverter jwtAuthenticationConverter; - - @BeforeEach - void setUp() { - when(oidcConfiguration.get(ConfigItem.AUTH_URL)).thenReturn( - Optional - .of("https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/auth")); - when(oidcConfiguration.get(ConfigItem.TOKEN_URL)).thenReturn( - Optional - .of("https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/token")); - when(oidcConfiguration.get(ConfigItem.REVOKE_URL)).thenReturn( - Optional - .of("https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/revoke")); - } - @Test void capabilityStatement() { final String response = restTemplate @@ -178,4 +161,22 @@ static class SmartConfiguration { } + @TestConfiguration + public static class AuthorizationConfigurationTestDependencies { + + @Bean + @Primary + OidcConfiguration oidcConfiguration() { + final Map oidcConfiguration = new HashMap<>(); + oidcConfiguration.put(ConfigItem.AUTH_URL.getKey(), + "https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/auth"); + oidcConfiguration.put(ConfigItem.TOKEN_URL.getKey(), + "https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/token"); + oidcConfiguration.put(ConfigItem.REVOKE_URL.getKey(), + "https://auth.ontoserver.csiro.au/auth/realms/aehrc/protocol/openid-connect/revoke"); + return new OidcConfiguration(oidcConfiguration); + } + + } + } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/CapabilityStatementTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/CapabilityStatementTest.java index eecd0f23c6..551e4c8b88 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/CapabilityStatementTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/CapabilityStatementTest.java @@ -18,7 +18,10 @@ package au.csiro.pathling.test.integration; import static au.csiro.pathling.test.TestResources.assertJson; +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.Arrays; +import java.util.Collections; import org.json.JSONException; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; @@ -29,11 +32,19 @@ import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.test.context.TestPropertySource; /** * @author John Grimes */ +@TestPropertySource(properties = { + "pathling.cors.maxAge=800", + "pathling.cors.allowedMethods=GET,POST", + "pathling.cors.allowedOrigins=http://foo.bar,http://boo.bar", + "pathling.cors.allowedHeaders=X-Mine,X-Other" +}) @Tag("Tranche2") class CapabilityStatementTest extends IntegrationTest { @@ -56,12 +67,20 @@ void cors() throws JSONException { final HttpHeaders corsHeaders = new HttpHeaders(); corsHeaders.setOrigin("http://foo.bar"); corsHeaders.setAccessControlRequestMethod(HttpMethod.GET); + corsHeaders.setAccessControlRequestHeaders(Arrays.asList("X-Mine", "X-Skip")); final ResponseEntity response = restTemplate.exchange( "http://localhost:" + port + "/fhir/metadata", HttpMethod.OPTIONS, new HttpEntity(corsHeaders), String.class); - System.out.println(response); + final HttpHeaders responseHeaders = response.getHeaders(); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals("http://foo.bar", responseHeaders.getAccessControlAllowOrigin()); + assertEquals(Arrays.asList(HttpMethod.GET, HttpMethod.POST), + responseHeaders.getAccessControlAllowMethods()); + assertEquals(800L, responseHeaders.getAccessControlMaxAge()); + assertEquals(Collections.singletonList("X-Mine"), + responseHeaders.getAccessControlAllowHeaders()); } } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/OperationDefinitionTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/OperationDefinitionTest.java index f7784de370..9d2e81ca65 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/OperationDefinitionTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/OperationDefinitionTest.java @@ -49,7 +49,7 @@ class OperationDefinitionTest extends IntegrationTest { private static final List OPERATIONS = List.of("aggregate", "search", "extract", "import", "result", "job"); - private static final String SUFFIX = "6"; + private static final String SUFFIX = "7"; @Test void operationDefinitions() throws MalformedURLException, URISyntaxException { diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SentryTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SentryTest.java index a9953962de..446e8508c8 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/SentryTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/SentryTest.java @@ -38,7 +38,7 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; @@ -72,7 +72,7 @@ void reportsToSentry() throws URISyntaxException, InterruptedException { null); final ResponseEntity response = restTemplate .exchange(uri, HttpMethod.GET, RequestEntity.get(uri).build(), String.class); - final HttpStatus statusCode = response.getStatusCode(); + final HttpStatusCode statusCode = response.getStatusCode(); assertTrue(statusCode.is5xxServerError()); // Give the asynchronous request sender within Sentry time to actually send the error report. diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/TerminologyServiceWithLanguageIntegrationTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/TerminologyServiceWithLanguageIntegrationTest.java index 1d637f7679..6169cbf145 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/TerminologyServiceWithLanguageIntegrationTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/TerminologyServiceWithLanguageIntegrationTest.java @@ -19,13 +19,11 @@ import static au.csiro.pathling.test.helpers.TerminologyHelpers.LC_29463_7; import static au.csiro.pathling.test.helpers.TerminologyHelpers.LC_55915_3; -import static com.github.tomakehurst.wiremock.client.WireMock.containing; import static com.github.tomakehurst.wiremock.client.WireMock.proxyAllTo; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static org.junit.jupiter.api.Assertions.assertEquals; import au.csiro.pathling.io.CacheableDatabase; -import au.csiro.pathling.terminology.DefaultTerminologyService; import au.csiro.pathling.terminology.DefaultTerminologyServiceFactory; import au.csiro.pathling.terminology.TerminologyService; import au.csiro.pathling.terminology.TerminologyService.Property; @@ -42,7 +40,6 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/integration/modification/ImportTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/integration/modification/ImportTest.java index 975749dc68..90d39f3918 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/integration/modification/ImportTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/integration/modification/ImportTest.java @@ -19,8 +19,8 @@ import static au.csiro.pathling.test.TestResources.getResourceAsUrl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import au.csiro.pathling.errors.ErrorHandlingInterceptor; import au.csiro.pathling.errors.InvalidUserInputError; @@ -203,7 +203,7 @@ void throwsOnMissingId() { () -> importExecutor.execute(buildImportParameters(jsonURL, ResourceType.PATIENT))); final BaseServerResponseException convertedError = ErrorHandlingInterceptor.convertError(error); - assertTrue(convertedError instanceof InvalidRequestException); + assertInstanceOf(InvalidRequestException.class, convertedError); assertEquals("Encountered a resource with no ID", convertedError.getMessage()); } diff --git a/fhir-server/src/test/java/au/csiro/pathling/test/system/DockerImageTest.java b/fhir-server/src/test/java/au/csiro/pathling/test/system/DockerImageTest.java index f5a6430440..768d2e1bc9 100644 --- a/fhir-server/src/test/java/au/csiro/pathling/test/system/DockerImageTest.java +++ b/fhir-server/src/test/java/au/csiro/pathling/test/system/DockerImageTest.java @@ -153,7 +153,7 @@ static File[] getResourceFolderFiles( final URL url = loader.getResource(folder); assertThat(url).isNotNull(); final String path = url.getPath(); - final FileFilter fileFilter = new WildcardFileFilter("*.ndjson"); + final FileFilter fileFilter = WildcardFileFilter.builder().setWildcards("*.ndjson").get(); @Nullable final File[] files = new File(path).listFiles(fileFilter); assertNotNull(files); return files; diff --git a/fhir-server/src/test/resources/application-unit-test.yml b/fhir-server/src/test/resources/application-unit-test.yml new file mode 100644 index 0000000000..0638655e6b --- /dev/null +++ b/fhir-server/src/test/resources/application-unit-test.yml @@ -0,0 +1,7 @@ +spark: + ui: + enabled: false + +spring: + main: + banner-mode: off diff --git a/fhir-server/src/test/resources/logback-test.xml b/fhir-server/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..58d982a435 --- /dev/null +++ b/fhir-server/src/test/resources/logback-test.xml @@ -0,0 +1,35 @@ + + + + + + + [%level] %logger{36} - %msg%n + + + + + + + + + + + + + diff --git a/fhir-server/src/test/resources/responses/CapabilityStatementTest/capabilityStatement.CapabilityStatement.json b/fhir-server/src/test/resources/responses/CapabilityStatementTest/capabilityStatement.CapabilityStatement.json index 1880c7b2d1..ca88baa797 100644 --- a/fhir-server/src/test/resources/responses/CapabilityStatementTest/capabilityStatement.CapabilityStatement.json +++ b/fhir-server/src/test/resources/responses/CapabilityStatementTest/capabilityStatement.CapabilityStatement.json @@ -1,6 +1,6 @@ { "resourceType": "CapabilityStatement", - "url": "https://pathling.csiro.au/fhir/CapabilityStatement/pathling-fhir-api-6", + "url": "https://pathling.csiro.au/fhir/CapabilityStatement/pathling-fhir-api-7", "name": "pathling-fhir-api", "title": "Pathling FHIR API", "status": "active", @@ -40,15 +40,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -69,15 +69,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -98,15 +98,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -127,15 +127,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -156,15 +156,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -185,15 +185,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -214,15 +214,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -243,15 +243,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -272,15 +272,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -301,15 +301,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -330,15 +330,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -359,15 +359,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -388,15 +388,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -417,15 +417,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -446,15 +446,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -475,15 +475,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -504,15 +504,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -533,15 +533,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -562,15 +562,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -591,15 +591,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -620,15 +620,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -649,15 +649,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -678,15 +678,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -707,15 +707,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -736,15 +736,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -765,15 +765,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -794,15 +794,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -823,15 +823,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -852,15 +852,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -881,15 +881,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -910,15 +910,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -939,15 +939,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -968,15 +968,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -997,15 +997,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1026,15 +1026,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1055,15 +1055,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1084,15 +1084,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1113,15 +1113,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1142,15 +1142,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1171,15 +1171,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1200,15 +1200,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1229,15 +1229,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1258,15 +1258,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1287,15 +1287,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1316,15 +1316,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1345,15 +1345,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1374,15 +1374,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1403,15 +1403,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1432,15 +1432,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1461,15 +1461,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1490,15 +1490,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1519,15 +1519,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1548,15 +1548,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1577,15 +1577,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1606,15 +1606,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1635,15 +1635,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1664,15 +1664,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1693,15 +1693,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1722,15 +1722,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1751,15 +1751,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1780,15 +1780,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1809,15 +1809,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1838,15 +1838,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1867,15 +1867,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1896,15 +1896,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1925,15 +1925,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1954,15 +1954,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -1983,15 +1983,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2012,15 +2012,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2041,15 +2041,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2070,15 +2070,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2099,15 +2099,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2128,15 +2128,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2157,15 +2157,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2186,15 +2186,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2215,15 +2215,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2244,15 +2244,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2273,15 +2273,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2302,15 +2302,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2331,15 +2331,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2360,15 +2360,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2389,15 +2389,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2418,15 +2418,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2447,15 +2447,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2476,15 +2476,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2505,15 +2505,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2534,15 +2534,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2563,15 +2563,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2592,15 +2592,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2621,15 +2621,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2650,15 +2650,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2679,15 +2679,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2708,15 +2708,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2737,15 +2737,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2766,15 +2766,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2795,15 +2795,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2824,15 +2824,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2853,15 +2853,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2882,15 +2882,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2911,15 +2911,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2940,15 +2940,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2969,15 +2969,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -2998,15 +2998,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3027,15 +3027,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3056,15 +3056,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3085,15 +3085,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3114,15 +3114,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3143,15 +3143,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3172,15 +3172,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3201,15 +3201,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3230,15 +3230,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3259,15 +3259,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3288,15 +3288,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3317,15 +3317,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3346,15 +3346,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3375,15 +3375,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3404,15 +3404,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3433,15 +3433,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3462,15 +3462,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3491,15 +3491,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3520,15 +3520,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3549,15 +3549,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3578,15 +3578,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3607,15 +3607,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3636,15 +3636,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3665,15 +3665,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3694,15 +3694,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3723,15 +3723,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3752,15 +3752,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3781,15 +3781,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3810,15 +3810,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3839,15 +3839,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3868,15 +3868,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3897,15 +3897,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3926,15 +3926,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3955,15 +3955,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -3984,15 +3984,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -4013,15 +4013,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -4042,15 +4042,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -4071,15 +4071,15 @@ "operation": [ { "name": "aggregate", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/aggregate-7" }, { "name": "extract", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/extract-7" }, { "name": "fhirPath", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/search-7" } ] }, @@ -4101,15 +4101,15 @@ "operation": [ { "name": "import", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/import-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/import-7" }, { "name": "result", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/result-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/result-7" }, { "name": "job", - "definition": "https://pathling.csiro.au/fhir/OperationDefinition/job-6" + "definition": "https://pathling.csiro.au/fhir/OperationDefinition/job-7" } ] } diff --git a/fhirpath/pom.xml b/fhirpath/pom.xml index 9461f5f849..6c08e110ed 100644 --- a/fhirpath/pom.xml +++ b/fhirpath/pom.xml @@ -51,7 +51,7 @@ io.delta - delta-core_${pathling.scalaVersion} + delta-spark_${pathling.scalaVersion} provided @@ -59,6 +59,10 @@ hadoop-aws provided + + javax.servlet + javax.servlet-api + @@ -81,6 +85,11 @@ org.hibernate.validator hibernate-validator + + jakarta.validation + jakarta.validation-api + provided + com.google.code.findbugs @@ -253,51 +262,13 @@ + + + **/logback.xml + + - - - - com.google.cloud.tools - jib-maven-plugin - 3.3.2 - - - ${pathling.dockerBaseImage} - - - ${pathling.dockerArchitecture} - linux - - - - - ${pathling.fhirServerDockerRepo} - - ${pathling.fhirServerDockerTag} - ${project.version} - ${project.majorVersion} - ${git.commit.id} - - - - - ${project.version} - -Xmx2g -XX:MaxMetaspaceSize=400m -XX:ReservedCodeCacheSize=240m -Xss1m -Duser.timezone=UTC - - - Copyright © 2018-2023, Commonwealth Scientific and Industrial Research Organisation (CSIRO) ABN 41 687 119 230. Licensed under the CSIRO Open Source Software Licence Agreement. - John Grimes <John.Grimes@csiro.au> - - - 8080 - 4040 - - - - - - diff --git a/fhirpath/src/license/THIRD-PARTY.properties b/fhirpath/src/license/THIRD-PARTY.properties index c855b989f6..a5dd080b04 100644 --- a/fhirpath/src/license/THIRD-PARTY.properties +++ b/fhirpath/src/license/THIRD-PARTY.properties @@ -10,7 +10,6 @@ # - Apache License Version 2.0 # - Apache License, 2.0 # - Apache License, Version 2.0 -# - Apache License, version 2.0 # - Apache Software License 2.0 # - Apache Software License, version 1.1 # - Apache-2.0 @@ -24,22 +23,26 @@ # - BSD licence # - BSD-3-Clause # - Bouncy Castle Licence -# - CC0 +# - CDDL + GPLv2 with classpath exception # - COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 # - Dual license consisting of the CDDL v1.1 and GPL v2 # - EDL 1.0 # - EPL 2.0 +# - EPL-2.0 # - Eclipse Distribution License - v 1.0 # - Eclipse Public License - v 1.0 # - Eclipse Public License v2.0 # - GNU General Public License, version 2 # - GNU Lesser General Public License +# - GPL-2.0-with-classpath-exception # - GPL2 w/ CPE # - Indiana University Extreme! Lab Software License, vesion 1.1.1 # - LGPL 2.1 # - LGPL-2.1-or-later +# - MIT # - MIT License # - MIT license +# - MIT-0 # - MPL 1.1 # - Modified BSD # - New BSD License @@ -60,5 +63,5 @@ # Please fill the missing licenses for dependencies : # # -#Fri Dec 01 16:33:30 AEST 2023 +#Mon May 20 15:43:36 AEST 2024 oro--oro--2.0.8= diff --git a/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java b/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java index 2e8cbc537d..0556581535 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java +++ b/fhirpath/src/main/java/au/csiro/pathling/config/QueryConfiguration.java @@ -17,11 +17,10 @@ package au.csiro.pathling.config; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; -import javax.validation.constraints.NotNull; - /** * Represents configuration that controls the behaviour of query executors. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/config/StorageConfiguration.java b/fhirpath/src/main/java/au/csiro/pathling/config/StorageConfiguration.java index f01dc6081b..1dd482df1a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/config/StorageConfiguration.java +++ b/fhirpath/src/main/java/au/csiro/pathling/config/StorageConfiguration.java @@ -17,11 +17,12 @@ package au.csiro.pathling.config; -import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; +import jakarta.annotation.Nonnull; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java index a2ce6199cd..34c659f746 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/extract/ExtractQueryExecutor.java @@ -20,6 +20,7 @@ import au.csiro.pathling.view.UnnestingSelection; import ca.uhn.fhir.context.FhirContext; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingComparator.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingComparator.java index 0f0bf0c260..81f768308c 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingComparator.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/comparison/CodingComparator.java @@ -25,6 +25,7 @@ import au.csiro.pathling.fhirpath.operator.Comparable; import au.csiro.pathling.fhirpath.operator.Comparable.ComparisonOperation; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.util.Arrays; import java.util.List; import java.util.function.Function; diff --git a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java index eb9fb872a8..9f98b1320d 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java +++ b/fhirpath/src/main/java/au/csiro/pathling/fhirpath/parser/Visitor.java @@ -27,6 +27,7 @@ import au.csiro.pathling.fhirpath.operator.BinaryOperatorType; import au.csiro.pathling.fhirpath.operator.CollectionOperations; import au.csiro.pathling.fhirpath.operator.MethodDefinedOperator; +import au.csiro.pathling.fhirpath.operator.SubsettingOperations; import au.csiro.pathling.fhirpath.parser.generated.FhirPathBaseVisitor; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AdditiveExpressionContext; import au.csiro.pathling.fhirpath.parser.generated.FhirPathParser.AndExpressionContext; @@ -190,7 +191,7 @@ public FhirPath visitIndexerExpression( new Visitor().visit(ctx.expression(1)), // Get a wrapped version of the index operator. MethodDefinedOperator.build( - CollectionOperations.class.getDeclaredMethod("index", Collection.class, + SubsettingOperations.class.getDeclaredMethod("index", Collection.class, IntegerCollection.class))); } catch (final NoSuchMethodException e) { throw new RuntimeException(e); diff --git a/fhirpath/src/main/java/au/csiro/pathling/sql/misc/TemporalDifferenceFunction.java b/fhirpath/src/main/java/au/csiro/pathling/sql/misc/TemporalDifferenceFunction.java index 305e23e7f5..cb7063aec8 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/sql/misc/TemporalDifferenceFunction.java +++ b/fhirpath/src/main/java/au/csiro/pathling/sql/misc/TemporalDifferenceFunction.java @@ -23,6 +23,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.time.LocalDate; +import java.time.Year; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeParseException; @@ -105,8 +106,12 @@ public static ZonedDateTime parse(final @Nonnull String encodedFrom) { try { return LocalDate.parse(encodedFrom).atStartOfDay(ZoneId.of("UTC")); } catch (final DateTimeParseException ex) { - // If we can't parse the value as a date or datetime, return null. - return null; + try { + return Year.parse(encodedFrom).atDay(1).atStartOfDay(ZoneId.of("UTC")); + } catch (final DateTimeParseException exc) { + // If we can't parse the value as a date or datetime, return null. + return null; + } } } } diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/Column.java b/fhirpath/src/main/java/au/csiro/pathling/views/Column.java index 428dc697ec..f04c4b3c3a 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/Column.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/Column.java @@ -1,9 +1,9 @@ package au.csiro.pathling.views; import jakarta.annotation.Nullable; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ColumnSelect.java b/fhirpath/src/main/java/au/csiro/pathling/views/ColumnSelect.java index 07c991467b..afb3ad62b5 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ColumnSelect.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ColumnSelect.java @@ -2,10 +2,10 @@ import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.Collections; import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java index 496073f0ee..0d9d98d130 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ConstantDeclaration.java @@ -1,7 +1,7 @@ package au.csiro.pathling.views; import jakarta.annotation.Nonnull; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.Data; import org.hl7.fhir.instance.model.api.IBase; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java index 579b14a5ea..fe46fa5858 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/FhirView.java @@ -1,9 +1,9 @@ package au.csiro.pathling.views; import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; import lombok.Data; /** diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelect.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelect.java index db5692f069..9040ecfb90 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelect.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachOrNullSelect.java @@ -1,10 +1,10 @@ package au.csiro.pathling.views; +import com.google.gson.annotations.SerializedName; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.Collections; import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; -import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -30,8 +30,8 @@ public class ForEachOrNullSelect extends SelectClause { String path; @NotNull - List column = Collections.emptyList(); - + List column = Collections.emptyList(); + /** * Nested select relative to the {@link #path}. * diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelect.java b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelect.java index d758e0aeeb..60a60e9bea 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelect.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/ForEachSelect.java @@ -1,10 +1,10 @@ package au.csiro.pathling.views; +import com.google.gson.annotations.SerializedName; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.Collections; import java.util.List; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; -import com.google.gson.annotations.SerializedName; import lombok.Data; import lombok.EqualsAndHashCode; diff --git a/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java index cb78bcd7bd..67f8ecaae0 100644 --- a/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java +++ b/fhirpath/src/main/java/au/csiro/pathling/views/WhereClause.java @@ -2,7 +2,7 @@ import com.google.gson.annotations.SerializedName; import jakarta.annotation.Nullable; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** diff --git a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java index b1e8f806c9..c9dd3129a6 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/fhirpath/function/UntilFunctionTest.java @@ -275,6 +275,39 @@ class UntilFunctionTest { // } // // @Test + // void yearOnlyDateInput() throws ParseException { + // final Dataset leftDataset = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.StringType) + // .withRow("patient-1", "2020") + // .build(); + // final ElementPath input = new ElementPathBuilder(spark) + // .fhirType(FHIRDefinedType.DATE) + // .dataset(leftDataset) + // .idAndValueColumns() + // .singular(true) + // .build(); + // final DateTimeLiteralPath argument = DateTimeLiteralPath.fromString("2020-01-02T00:00:00Z", + // input); + // final ParserContext context = new ParserContextBuilder(spark, fhirContext) + // .groupingColumns(Collections.singletonList(input.getIdColumn())) + // .build(); + // final Dataset expectedResult = new DatasetBuilder(spark) + // .withIdColumn(ID_ALIAS) + // .withColumn(DataTypes.IntegerType) + // .withRow("patient-1", 86400000) + // .build(); + // final List arguments = List.of(argument, + // StringLiteralPath.fromString("millisecond", input)); + // final NamedFunctionInput functionInput = new NamedFunctionInput(context, input, arguments); + // final FhirPath result = NamedFunction.getInstance("until").invoke(functionInput); + // assertThat(result) + // .isElementPath(IntegerPath.class) + // .selectResult() + // .hasRows(expectedResult); + // } + // + // @Test // void invalidCalendarDuration() { // final Dataset leftDataset = new DatasetBuilder(spark) // .withIdColumn(ID_ALIAS) diff --git a/fhirpath/src/test/java/au/csiro/pathling/sql/udf/TerminologyUdfTest.java b/fhirpath/src/test/java/au/csiro/pathling/sql/udf/TerminologyUdfTest.java index 383cac8b82..483816ac81 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/sql/udf/TerminologyUdfTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/sql/udf/TerminologyUdfTest.java @@ -319,8 +319,8 @@ public void testTranslateCoding() { final Dataset expectedResult = DatasetBuilder.of(spark).withIdColumn("id") .withColumn("result", TranslateUdf.RETURN_TYPE) .withRow("uc-null", null) - .withRow("uc-coding_1", CodingEncoding.encodeList(Arrays.asList(CODING_5, CODING_4))) - .withRow("uc-coding_2", Collections.emptyList()) + .withRow("uc-coding_1", CodingEncoding.encodeListToArray(List.of(CODING_5, CODING_4))) + .withRow("uc-coding_2", new Row[]{}) .build(); DatasetAssert.of(result).hasRows(expectedResult); } @@ -334,11 +334,11 @@ public void testTranslateCodingArray() { .withIdColumn("id") .withColumn("codings", DataTypes.createArrayType(CodingEncoding.DATA_TYPE)) .withRow("uc-null", null) - .withRow("uc-empty", Collections.emptyList()) - .withRow("uc-coding_1", Collections.singletonList(CodingEncoding.encode(CODING_1))) - .withRow("uc-coding_2", Collections.singletonList(CodingEncoding.encode(CODING_2))) - .withRow("uc-coding_1+coding_1", Arrays.asList(CodingEncoding.encode(CODING_1), - CodingEncoding.encode(CODING_1))) + .withRow("uc-empty", new Row[]{}) + .withRow("uc-coding_1", new Row[]{CodingEncoding.encode(CODING_1)}) + .withRow("uc-coding_2", new Row[]{CodingEncoding.encode(CODING_2)}) + .withRow("uc-coding_1+coding_1", + CodingEncoding.encodeListToArray(List.of(CODING_1, CODING_1))) .build(); final Dataset result = ds.select(ds.col("id"), @@ -350,11 +350,11 @@ public void testTranslateCodingArray() { final Dataset expectedResult = DatasetBuilder.of(spark).withIdColumn("id") .withColumn("result", TranslateUdf.RETURN_TYPE) .withRow("uc-null", null) - .withRow("uc-empty", Collections.emptyList()) - .withRow("uc-coding_1", CodingEncoding.encodeList(Arrays.asList(CODING_5, CODING_4))) - .withRow("uc-coding_2", Collections.emptyList()) + .withRow("uc-empty", new Row[]{}) + .withRow("uc-coding_1", CodingEncoding.encodeListToArray(List.of(CODING_5, CODING_4))) + .withRow("uc-coding_2", new Row[]{}) .withRow("uc-coding_1+coding_1", - CodingEncoding.encodeList(Arrays.asList(CODING_5, CODING_4))) + CodingEncoding.encodeListToArray(List.of(CODING_5, CODING_4))) .build(); DatasetAssert.of(result).hasRows(expectedResult); } @@ -514,8 +514,8 @@ public void testDisplayWithLanguage() { @ParameterizedTest @MethodSource("propertyParameters") public void testProperty(final String propertyType, final DataType resultDataType, - final Type[] propertyAFhirValues, final Type[] propertyBFhirValues, - final Object[] propertyASqlValues, final Object[] propertyBSqlValues) { + final List propertyAFhirValues, final List propertyBFhirValues, + final List propertyASqlValues, final List propertyBSqlValues) { TerminologyServiceHelpers.setupLookup(terminologyService) .withProperty(CODING_1, "property_a", null, propertyAFhirValues) .withProperty(CODING_2, "property_b", null, propertyBFhirValues); @@ -536,8 +536,8 @@ public void testProperty(final String propertyType, final DataType resultDataTyp .withColumn("result", DataTypes.createArrayType(resultDataType)) .withRow("uc-null", null) .withRow("uc-invalid", null) - .withRow("uc-codingA", Arrays.asList(propertyASqlValues)) - .withRow("uc-codingB", Collections.emptyList()) + .withRow("uc-codingA", propertyASqlValues.toArray()) + .withRow("uc-codingB", new Object[]{}) .build(); DatasetAssert.of(resultA) @@ -552,8 +552,8 @@ public void testProperty(final String propertyType, final DataType resultDataTyp .withColumn("result", DataTypes.createArrayType(resultDataType)) .withRow("uc-null", null) .withRow("uc-invalid", null) - .withRow("uc-codingA", Collections.emptyList()) - .withRow("uc-codingB", Arrays.asList(propertyBSqlValues)) + .withRow("uc-codingA", new Object[]{}) + .withRow("uc-codingB", propertyBSqlValues.toArray()) .build(); DatasetAssert.of(resultB) @@ -563,7 +563,7 @@ public void testProperty(final String propertyType, final DataType resultDataTyp @Test public void testPropertyWithDefaultType() { TerminologyServiceHelpers.setupLookup(terminologyService) - .withProperty(CODING_1, "property_a", (String) null, new StringType("value_a")); + .withProperty(CODING_1, "property_a", null, List.of(new StringType("value_a"))); final Dataset ds = DatasetBuilder.of(spark) .withIdColumn("id") @@ -590,8 +590,8 @@ public void testPropertyWithDefaultType() { @Test public void testPropertyWithLanguage() { TerminologyServiceHelpers.setupLookup(terminologyService) - .withProperty(CODING_1, "property_a", "de", new StringType("value_a_de")) - .withProperty(CODING_2, "property_b", "fr", new StringType("value_b_fr")); + .withProperty(CODING_1, "property_a", "de", List.of(new StringType("value_a_de"))) + .withProperty(CODING_2, "property_b", "fr", List.of(new StringType("value_b_fr"))); final Dataset ds = DatasetBuilder.of(spark) .withIdColumn("id") diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/SpringBootUnitTest.java b/fhirpath/src/test/java/au/csiro/pathling/test/SpringBootUnitTest.java index 954aa491b8..9aaae4b1bf 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/SpringBootUnitTest.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/SpringBootUnitTest.java @@ -2,14 +2,13 @@ import au.csiro.pathling.UnitTestDependencies; -import org.junit.jupiter.api.Tag; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.ActiveProfiles; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java index 81309def6f..af5fd211be 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/assertions/Assertions.java @@ -21,6 +21,7 @@ import au.csiro.pathling.fhirpath.execution.CollectionDataset; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java index f8e2b3b68f..e047ad38f4 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/builders/ElementPathBuilder.java @@ -28,6 +28,7 @@ import au.csiro.pathling.test.helpers.SparkHelpers.IdAndValueColumns; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; +import java.util.Optional; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; diff --git a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java index f2642a38d0..40e21b58bc 100644 --- a/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java +++ b/fhirpath/src/test/java/au/csiro/pathling/test/helpers/SparkHelpers.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import lombok.Value; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; diff --git a/fhirpath/src/test/resources/logback-spring.xml b/fhirpath/src/test/resources/logback-spring.xml deleted file mode 100644 index 38bfcfb1d3..0000000000 --- a/fhirpath/src/test/resources/logback-spring.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - [%level] %logger{36} - %msg%n - - - - target/timing.log - - [%d] %msg%n - - - - - - - - - - - diff --git a/fhirpath/src/test/resources/logback.xml b/fhirpath/src/test/resources/logback.xml index 71e0ba327c..0a2a613af8 100644 --- a/fhirpath/src/test/resources/logback.xml +++ b/fhirpath/src/test/resources/logback.xml @@ -31,7 +31,10 @@ - + + + + diff --git a/lib/R/DESCRIPTION.src b/lib/R/DESCRIPTION.src index 19b3b1d786..04f9bc5dad 100644 --- a/lib/R/DESCRIPTION.src +++ b/lib/R/DESCRIPTION.src @@ -26,12 +26,12 @@ Encoding: UTF-8 Depends: R (>= 3.5.0) Imports: - rlang(>= 1.0.0), sparklyr(>= 1.8.4) + rlang(>= 1.0.0), sparklyr(>= 1.8.1) Suggests: - testthat(>= 3.0.0) + testthat(>= 3.2.1.1) Config/testthat/edition: 3 RoxygenNote: 7.2.3 -SystemRequirements: Spark: 3.4.x +SystemRequirements: Spark: 3.5.x Config/pathling/Version: %pathling.version% Config/pathling/SparkVersion: %pathling.Rapi.sparkVersion% Config/pathling/ScalaVersion: %pathling.Rapi.scalaVersion% diff --git a/lib/R/R/context.R b/lib/R/R/context.R index 9565afbc8a..5f5bc157df 100644 --- a/lib/R/R/context.R +++ b/lib/R/R/context.R @@ -146,12 +146,6 @@ pathling_connect <- function( new_spark_connection() } - spark_runtime_version <- sparklyr::spark_version(spark) - if (package_version(spark_runtime_version) < spark_info$spark_version) { - warning(sprintf("Incompatible version of spark: %s, while Pathling requires at least: %s", - spark_runtime_version, spark_info$spark_version)) - } - encoders_config <- spark %>% j_invoke_static( "au.csiro.pathling.config.EncodingConfiguration", "builder") %>% diff --git a/lib/R/R/datasource.R b/lib/R/R/datasource.R index 8248ae8ca6..131ba6d712 100644 --- a/lib/R/R/datasource.R +++ b/lib/R/R/datasource.R @@ -39,6 +39,22 @@ ImportMode <- list( MERGE = "merge" ) +#' SaveMode +#' +#' The following save modes are supported: +#' \itemize{ +#' \item{\code{OVERWRITE}: Overwrite any existing data.} +#' \item{\code{APPEND}: Append the new data to the existing data.} +#' \item{\code{IGNORE}: Only save the data if the file does not already exist.} +#' \item{\code{ERROR}: Raise an error if the file already exists.} +#' } +SaveMode <- list( + OVERWRITE = "overwrite", + APPEND = "append", + IGNORE = "ignore", + ERROR = "error" +) + #' Create a data source from NDJSON #' #' Creates a data source from a directory containing NDJSON files. The files must be named with the @@ -250,6 +266,7 @@ invoke_datasink <- function(ds, name, ...) { #' #' @param ds The DataSource object. #' @param path The URI of the directory to write the files to. +#' @param save_mode The save mode to use when writing the data. #' @param file_name_mapper An optional function that can be used to customise the mapping #' of the resource type to the file name. Currently not implemented. #' @@ -269,10 +286,10 @@ invoke_datasink <- function(ds, name, ...) { #' @family data sink functions #' #' @export -ds_write_ndjson <- function(ds, path, file_name_mapper = NULL) { +ds_write_ndjson <- function(ds, path, save_mode = SaveMode$ERROR, file_name_mapper = NULL) { #See: issue #1601 (Implement file_name_mappers in R sparkly API) stopifnot(file_name_mapper == NULL) - invoke_datasink(ds, "ndjson", path) + invoke_datasink(ds, "ndjson", path, save_mode) } #' Write FHIR data to Parquet files @@ -281,6 +298,7 @@ ds_write_ndjson <- function(ds, path, file_name_mapper = NULL) { #' #' @param ds The DataSource object. #' @param path The URI of the directory to write the files to. +#' @param save_mode The save mode to use when writing the data. #' #' @return No return value, called for side effects only. #' @@ -298,8 +316,8 @@ ds_write_ndjson <- function(ds, path, file_name_mapper = NULL) { #' @family data sink functions #' #' @export -ds_write_parquet <- function(ds, path) { - invoke_datasink(ds, "parquet", path) +ds_write_parquet <- function(ds, path, save_mode = SaveMode$ERROR) { + invoke_datasink(ds, "parquet", path, save_mode) } #' Write FHIR data to Delta files diff --git a/lib/R/R/dependencies.R b/lib/R/R/dependencies.R index 3540621b21..c792c675a4 100644 --- a/lib/R/R/dependencies.R +++ b/lib/R/R/dependencies.R @@ -18,7 +18,7 @@ spark_dependencies <- function(spark_version, scala_version, ...) { sparklyr::spark_dependency( packages = c( paste0("au.csiro.pathling:library-runtime:", pathling_version()), - paste0("io.delta:delta-core_", spark_info$scala_version, ":", spark_info$delta_version), + paste0("io.delta:delta-spark_", spark_info$scala_version, ":", spark_info$delta_version), paste0("org.apache.hadoop:hadoop-aws:", spark_info$hadoop_version) ) ) diff --git a/lib/R/README.md b/lib/R/README.md index 9c3f6bf8b9..3b5c7457b5 100644 --- a/lib/R/README.md +++ b/lib/R/README.md @@ -2,7 +2,7 @@ R API for Pathling ================== ``pathling`` package is the R API for [Pathling](https://pathling.csiro.au), -based on [sparklyr](https://spark.rstudio.com/). It provides a set of functions +based on [sparklyr](https://spark.posit.co/). It provides a set of functions that aid the use of FHIR terminology services and FHIR data within R code. ## Local installation diff --git a/lib/R/pom.xml b/lib/R/pom.xml index c454b94ac7..8a75b30582 100644 --- a/lib/R/pom.xml +++ b/lib/R/pom.xml @@ -63,7 +63,7 @@ of the sparklyr version that we depend upon that is on the archive site. The reason we can't use the latest from the CDN is that the files drop off the CDN when a new version is released. --> - 3.4.0 + 3.5.1 ${pathling.scalaVersion} ${pathling.hadoopMajorVersion} ${pathling.hadoopVersion} @@ -225,6 +225,21 @@ + + generate-docs + process-sources + + exec + + + Rscript + ${project.basedir} + + -e + roxygen2::roxygenise() + + + test test @@ -308,21 +323,6 @@ org.codehaus.mojo exec-maven-plugin - - generate-docs - process-sources - - exec - - - Rscript - ${project.basedir} - - -e - roxygen2::roxygenise() - - - generate-site process-sources diff --git a/lib/R/src/license/THIRD-PARTY.properties b/lib/R/src/license/THIRD-PARTY.properties index 3716e1a8e6..00719fb2e0 100644 --- a/lib/R/src/license/THIRD-PARTY.properties +++ b/lib/R/src/license/THIRD-PARTY.properties @@ -9,7 +9,6 @@ # - Apache License V2.0 # - Apache License, 2.0 # - Apache License, Version 2.0 -# - Apache License, version 2.0 # - Apache Software License 2.0 # - Apache Software License, version 1.1 # - Apache-2.0 @@ -21,19 +20,22 @@ # - BSD 3-clause # - BSD licence # - BSD-3-Clause -# - CC0 +# - CDDL + GPLv2 with classpath exception # - COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 # - Dual license consisting of the CDDL v1.1 and GPL v2 # - EDL 1.0 # - EPL 2.0 +# - EPL-2.0 # - Eclipse Public License - v 1.0 # - GNU General Public License, version 2 # - GNU Lesser General Public License +# - GPL-2.0-with-classpath-exception # - GPL2 w/ CPE # - Indiana University Extreme! Lab Software License, vesion 1.1.1 # - LGPL 2.1 # - MIT License # - MIT license +# - MIT-0 # - MPL 1.1 # - Modified BSD # - New BSD License @@ -53,5 +55,5 @@ # Please fill the missing licenses for dependencies : # # -#Fri Dec 01 16:35:15 AEST 2023 +#Mon May 20 15:45:54 AEST 2024 oro--oro--2.0.8= diff --git a/lib/import/azure-pipelines.yml b/lib/import/azure-pipelines.yml index 69f0bff1c2..d3a5cceac0 100644 --- a/lib/import/azure-pipelines.yml +++ b/lib/import/azure-pipelines.yml @@ -34,7 +34,7 @@ stages: options: "-pl lib/import -am" mavenOptions: "$(mavenOptions)" javaHomeOption: "JDKVersion" - jdkVersionOption: "1.11" + jdkVersionOption: "1.17" jdkArchitectureOption: "x64" goals: "package" timeoutInMinutes: 5 diff --git a/lib/python/README.md b/lib/python/README.md index 42f38cfb05..242fcac9a2 100644 --- a/lib/python/README.md +++ b/lib/python/README.md @@ -271,11 +271,11 @@ Maven package. Once the cluster is restarted, the libraries should be available for import and use within all notebooks. By default, Databricks uses Java 8 within its clusters, while Pathling requires -Java 11. To enable Java 11 support within your cluster, navigate to __Advanced +Java 17. To enable Java 17 support within your cluster, navigate to __Advanced Options > Spark > Environment Variables__ and add the following: ```bash -JNAME=zulu11-ca-amd64 +JNAME=zulu17-ca-amd64 ``` See the Databricks documentation on diff --git a/lib/python/examples/query.py b/lib/python/examples/query.py index f52e20db48..5dab019bf3 100755 --- a/lib/python/examples/query.py +++ b/lib/python/examples/query.py @@ -14,9 +14,8 @@ # limitations under the License. import os -from tempfile import mkdtemp - from pyspark.sql import DataFrame, SparkSession +from tempfile import mkdtemp from pathling import PathlingContext, DataSource, Expression as exp from pathling._version import __java_version__ @@ -36,7 +35,7 @@ spark = ( SparkSession.builder.config( "spark.jars.packages", - f"au.csiro.pathling:library-runtime:{__java_version__},io.delta:delta-core_2.12:2.2.0", + f"au.csiro.pathling:library-runtime:{__java_version__},io.delta:delta-spark_2.12:3.2.0", ) .config( "spark.sql.extensions", diff --git a/lib/python/pathling/context.py b/lib/python/pathling/context.py index fb887615c8..72e0e27e3f 100644 --- a/lib/python/pathling/context.py +++ b/lib/python/pathling/context.py @@ -184,7 +184,7 @@ def _new_spark_session(): SparkSession.builder.config( "spark.jars.packages", f"au.csiro.pathling:library-runtime:{__java_version__}," - f"io.delta:delta-core_{__scala_version__}:{__delta_version__}," + f"io.delta:delta-spark_{__scala_version__}:{__delta_version__}," f"org.apache.hadoop:hadoop-aws:{__hadoop_version__}", ) .config( diff --git a/lib/python/pathling/datasink.py b/lib/python/pathling/datasink.py index 1f1e00573f..d8d6c47d69 100644 --- a/lib/python/pathling/datasink.py +++ b/lib/python/pathling/datasink.py @@ -28,6 +28,22 @@ class ImportMode: MERGE: str = "merge" +class SaveMode: + """ + Constants that represent the different save modes. + + OVERWRITE: Overwrite any existing data. + APPEND: Append the new data to the existing data. + IGNORE: Only save the data if the file does not already exist. + ERROR: Raise an error if the file already exists. + """ + + OVERWRITE: str = "overwrite" + APPEND: str = "append" + IGNORE: str = "ignore" + ERROR: str = "error" + + class DataSinks(SparkConversionsMixin): """ A class for writing FHIR data to a variety of different targets. @@ -41,12 +57,22 @@ def __init__(self, datasource: DataSource): ) ) - def ndjson(self, path: str, file_name_mapper: Callable[[str], str] = None) -> None: + def ndjson( + self, + path: str, + save_mode: Optional[str] = SaveMode.ERROR, + file_name_mapper: Callable[[str], str] = None, + ) -> None: """ Writes the data to a directory of NDJSON files. The files will be named using the resource type and the ".ndjson" extension. :param path: The URI of the directory to write the files to. + :param save_mode: The save mode to use when writing the data: + - "overwrite" will overwrite any existing data. + - "append" will append the new data to the existing data. + - "ignore" will only save the data if the file does not already exist. + - "error" will raise an error if the file already exists. :param file_name_mapper: An optional function that can be used to customise the mapping of the resource type to the file name. """ @@ -54,17 +80,22 @@ def ndjson(self, path: str, file_name_mapper: Callable[[str], str] = None) -> No wrapped_mapper = StringMapper( self.spark._jvm._gateway_client, file_name_mapper ) - self._datasinks.ndjson(path, wrapped_mapper) + self._datasinks.ndjson(path, save_mode, wrapped_mapper) else: - self._datasinks.ndjson(path) + self._datasinks.ndjson(path, save_mode) - def parquet(self, path: str) -> None: + def parquet(self, path: str, save_mode: Optional[str] = SaveMode.ERROR) -> None: """ Writes the data to a directory of Parquet files. :param path: The URI of the directory to write the files to. + :param save_mode: The save mode to use when writing the data: + - "overwrite" will overwrite any existing data. + - "append" will append the new data to the existing data. + - "ignore" will only save the data if the file does not already exist. + - "error" will raise an error if the file already exists. """ - self._datasinks.parquet(path) + self._datasinks.parquet(path, save_mode) def delta( self, path: str, import_mode: Optional[str] = ImportMode.OVERWRITE diff --git a/lib/python/pom.xml b/lib/python/pom.xml index 608512e36f..9e883134f6 100644 --- a/lib/python/pom.xml +++ b/lib/python/pom.xml @@ -222,10 +222,8 @@ spark-submit - --jars - ${project.build.directory}/dependency/* --packages - au.csiro.pathling:library-runtime:${project.version},io.delta:delta-core_${pathling.scalaVersion}:${pathling.deltaVersion} + au.csiro.pathling:library-runtime:${project.version},io.delta:delta-spark_${pathling.scalaVersion}:${pathling.deltaVersion} --conf spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension --conf diff --git a/lib/python/requirements/dev.txt b/lib/python/requirements/dev.txt index 68e21c2110..7e6014def7 100644 --- a/lib/python/requirements/dev.txt +++ b/lib/python/requirements/dev.txt @@ -1,9 +1,9 @@ -r package.txt Sphinx>=1.6,<8 sphinx-rtd-theme==1.2.2 -pytest==7.4.0 +pytest==8.2.0 ipython==8.10.0 -wheel==0.40.0 -twine==4.0.2 -build==0.10.0 -pytest-cov==4.1.0 +wheel==0.43.0 +twine==5.0.0 +build==1.2.1 +pytest-cov==5.0.0 diff --git a/lib/python/requirements/package.txt b/lib/python/requirements/package.txt index 7de8ec0292..15af9fa8a4 100644 --- a/lib/python/requirements/package.txt +++ b/lib/python/requirements/package.txt @@ -1,2 +1,2 @@ -pyspark==3.4.1 +pyspark==3.5.1 deprecated==1.2.14 diff --git a/lib/python/setup.py b/lib/python/setup.py index 244e815bdc..291bfd50be 100644 --- a/lib/python/setup.py +++ b/lib/python/setup.py @@ -56,14 +56,13 @@ classifiers=[ "Development Status :: 3 - Alpha", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", ], license="Apache License, version 2.0", - python_requires=">=3.7", - install_requires=["pyspark>=3.4.0,<3.5.0", "deprecated>=1.2.13"], + python_requires=">=3.8", + install_requires=["pyspark>=3.5.0,<3.6.0", "deprecated>=1.2.13"], include_package_data=True, data_files=[ ("share/pathling/examples", glob.glob("examples/*.py")), diff --git a/lib/python/tests/conftest.py b/lib/python/tests/conftest.py index e3b249f6d6..3aee982b7c 100644 --- a/lib/python/tests/conftest.py +++ b/lib/python/tests/conftest.py @@ -74,7 +74,7 @@ def pathling_ctx(request, temp_warehouse_dir): .config( "spark.jars.packages", f"au.csiro.pathling:library-runtime:{__java_version__}," - f"io.delta:delta-core_{__scala_version__}:{__delta_version__}," + f"io.delta:delta-spark_{__scala_version__}:{__delta_version__}," f"org.apache.hadoop:hadoop-aws:{__hadoop_version__}", ) .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") diff --git a/library-api/pom.xml b/library-api/pom.xml index b4cd90aea5..42bad4487b 100644 --- a/library-api/pom.xml +++ b/library-api/pom.xml @@ -70,9 +70,13 @@ io.delta - delta-core_${pathling.scalaVersion} + delta-spark_${pathling.scalaVersion} provided + + javax.servlet + javax.servlet-api + org.apache.hadoop hadoop-client-api @@ -88,6 +92,11 @@ lombok provided + + org.apache.derby + derbytools + test + @@ -232,6 +241,11 @@ + + + **/logback.xml + + diff --git a/library-api/src/license/THIRD-PARTY.properties b/library-api/src/license/THIRD-PARTY.properties index d948b4656a..5175cd141a 100644 --- a/library-api/src/license/THIRD-PARTY.properties +++ b/library-api/src/license/THIRD-PARTY.properties @@ -9,7 +9,6 @@ # - Apache License V2.0 # - Apache License, 2.0 # - Apache License, Version 2.0 -# - Apache License, version 2.0 # - Apache Software License 2.0 # - Apache Software License, version 1.1 # - Apache v2 @@ -23,23 +22,28 @@ # - BSD License # - BSD licence # - BSD-3-Clause -# - CC0 +# - CDDL + GPLv2 with classpath exception # - COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 # - Dual license consisting of the CDDL v1.1 and GPL v2 # - EDL 1.0 # - EPL 2.0 +# - EPL-2.0 # - Eclipse Public License - v 1.0 # - Eclipse Public License v2.0 # - GNU General Public License, version 2 # - GNU Lesser General Public License +# - GPL-2.0-with-classpath-exception # - GPL2 w/ CPE # - Indiana University Extreme! Lab Software License, vesion 1.1.1 # - LGPL 2.1 +# - MIT # - MIT License # - MIT license +# - MIT-0 # - MPL 1.1 # - Modified BSD # - New BSD License +# - New BSD license # - Public Domain # - The Apache License, Version 2.0 # - The Apache Software License, Version 2.0 @@ -57,7 +61,7 @@ # Please fill the missing licenses for dependencies : # # -#Fri Dec 01 16:33:56 AEST 2023 +#Mon May 20 15:44:02 AEST 2024 javax.transaction--transaction-api--1.1= javax.transaction--jta--1.1= oro--oro--2.0.8= diff --git a/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java b/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java index d55262c453..ce722a40df 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java +++ b/library-api/src/main/java/au/csiro/pathling/library/PathlingContext.java @@ -171,8 +171,7 @@ public static PathlingContext create(@Nonnull final SparkSession sparkSession, @Nonnull public static PathlingContext create(@Nonnull final SparkSession sparkSession) { final EncodingConfiguration encodingConfig = EncodingConfiguration.builder().build(); - final TerminologyConfiguration terminologyConfig = TerminologyConfiguration.builder().build(); - return create(sparkSession, encodingConfig, terminologyConfig); + return create(sparkSession, encodingConfig); } /** @@ -250,10 +249,20 @@ public Dataset encode(@Nonnull final Dataset stringResourcesDF, return encode(stringResources, definition.getImplementingClass(), inputMimeType).toDF(); } + /** + * Takes a dataframe of encoded resources of the specified type and decodes them into a dataset of + * string representations, based on the requested output MIME type. + * + * @param resources the dataframe of encoded resources + * @param resourceName the name of the resources to decode + * @param outputMimeType the MIME type of the output strings + * @param the type of the resource + * @return a dataset of string representations of the resources + */ @Nonnull public Dataset decode(@Nonnull final Dataset resources, @Nonnull final String resourceName, @Nonnull final String outputMimeType) { - final BaseRuntimeElementDefinition definition = FhirEncoders.contextFor(fhirVersion) + final RuntimeResourceDefinition definition = FhirEncoders.contextFor(fhirVersion) .getResourceDefinition(resourceName); @SuppressWarnings("unchecked") @@ -261,7 +270,7 @@ public Dataset decode(@Nonnull final Dataset encoder = fhirEncoders.of(resourceClass); final Dataset typedResources = resources.as(encoder); - final DecodeResourceMapPartitions mapper = new DecodeResourceMapPartitions(fhirVersion, + final DecodeResourceMapPartitions mapper = new DecodeResourceMapPartitions<>(fhirVersion, outputMimeType, resourceClass); return typedResources.mapPartitions(mapper, Encoders.STRING()); diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/sink/DataSinkBuilder.java b/library-api/src/main/java/au/csiro/pathling/library/io/sink/DataSinkBuilder.java index b96ed1b5b6..72d2c0cbb5 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/sink/DataSinkBuilder.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/sink/DataSinkBuilder.java @@ -25,6 +25,8 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import java.util.function.UnaryOperator; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * This class knows how to take an @link{EnumerableDataSource} and write it to a variety of @@ -34,6 +36,15 @@ */ public class DataSinkBuilder { + @Nonnull + private static final Map SAVE_MODES = new ImmutableMap.Builder() + .put("error", SaveMode.ErrorIfExists) + .put("errorifexists", SaveMode.ErrorIfExists) + .put("overwrite", SaveMode.Overwrite) + .put("append", SaveMode.Append) + .put("ignore", SaveMode.Ignore) + .build(); + @Nonnull private final PathlingContext context; @@ -50,9 +61,16 @@ public DataSinkBuilder(@Nonnull final PathlingContext context, @Nonnull final Da * "ndjson" extension. * * @param path the directory to write the files to + * @param saveMode the save mode to use: + *
    + *
  • "error" - throw an error if the files already exist
  • + *
  • "overwrite" - overwrite any existing files
  • + *
  • "append" - append to any existing files
  • + *
  • "ignore" - do nothing if the files already exist
  • + *
*/ - public void ndjson(@Nullable final String path) { - new NdjsonSink(context, requireNonNull(path)).write(source); + public void ndjson(@Nullable final String path, @Nullable final String saveMode) { + new NdjsonSink(context, requireNonNull(path), resolveSaveMode(saveMode)).write(source); } /** @@ -60,11 +78,19 @@ public void ndjson(@Nullable final String path) { * custom file name mapper. * * @param path the directory to write the files to + * @param saveMode the save mode to use: + *
    + *
  • "error" - throw an error if the files already exist
  • + *
  • "overwrite" - overwrite any existing files
  • + *
  • "append" - append to any existing files
  • + *
  • "ignore" - do nothing if the files already exist
  • + *
* @param fileNameMapper a function that maps a resource type to a file name */ - public void ndjson(@Nullable final String path, + public void ndjson(@Nullable final String path, @Nullable final String saveMode, @Nullable final UnaryOperator fileNameMapper) { - new NdjsonSink(context, requireNonNull(path), requireNonNull(fileNameMapper)).write(source); + new NdjsonSink(context, requireNonNull(path), resolveSaveMode(saveMode), + requireNonNull(fileNameMapper)).write(source); } /** @@ -72,9 +98,16 @@ public void ndjson(@Nullable final String path, * "parquet" extension. * * @param path the directory to write the files to + * @param saveMode the save mode to use: + *
    + *
  • "error" - throw an error if the files already exist
  • + *
  • "overwrite" - overwrite any existing files
  • + *
  • "append" - append to any existing files
  • + *
  • "ignore" - do nothing if the files already exist
  • + *
*/ - public void parquet(@Nullable final String path) { - new ParquetSink(requireNonNull(path)).write(source); + public void parquet(@Nullable final String path, @Nullable final String saveMode) { + new ParquetSink(requireNonNull(path), resolveSaveMode(saveMode)).write(source); } /** @@ -134,4 +167,11 @@ public void tables(@Nullable final String importMode, @Nullable final String sch new CatalogSink(context, ImportMode.fromCode(importMode), requireNonNull(schema)).write(source); } + @Nonnull + private static SaveMode resolveSaveMode(final @Nullable String saveMode) { + return saveMode == null + ? SaveMode.ErrorIfExists + : SAVE_MODES.get(saveMode); + } + } diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/sink/NdjsonSink.java b/library-api/src/main/java/au/csiro/pathling/library/io/sink/NdjsonSink.java index fe0e55de0c..e8239cc3d3 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/sink/NdjsonSink.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/sink/NdjsonSink.java @@ -26,6 +26,7 @@ import jakarta.annotation.Nonnull; import java.util.function.UnaryOperator; import org.apache.spark.sql.Dataset; +import org.apache.spark.sql.SaveMode; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -42,26 +43,33 @@ public class NdjsonSink implements DataSink { @Nonnull private final String path; + @Nonnull + private final SaveMode saveMode; + @Nonnull private final UnaryOperator fileNameMapper; /** * @param context the {@link PathlingContext} to use * @param path the path to write the NDJSON files to + * @param saveMode the {@link SaveMode} to use */ - public NdjsonSink(@Nonnull final PathlingContext context, @Nonnull final String path) { - this(context, path, UnaryOperator.identity()); + public NdjsonSink(@Nonnull final PathlingContext context, @Nonnull final String path, + @Nonnull final SaveMode saveMode) { + this(context, path, saveMode, UnaryOperator.identity()); } /** * @param context the {@link PathlingContext} to use * @param path the path to write the NDJSON files to + * @param saveMode the {@link SaveMode} to use * @param fileNameMapper a function that maps resource type to file name */ public NdjsonSink(@Nonnull final PathlingContext context, @Nonnull final String path, - @Nonnull final UnaryOperator fileNameMapper) { + @Nonnull final SaveMode saveMode, @Nonnull final UnaryOperator fileNameMapper) { this.context = context; this.path = path; + this.saveMode = saveMode; this.fileNameMapper = fileNameMapper; } @@ -77,7 +85,7 @@ public void write(@Nonnull final DataSource source) { "ndjson"); final String resultUrl = safelyJoinPaths(path, fileName); final String resultUrlPartitioned = resultUrl + ".partitioned"; - jsonStrings.coalesce(1).write().text(resultUrlPartitioned); + jsonStrings.coalesce(1).write().mode(saveMode).text(resultUrlPartitioned); // Remove the partitioned directory and replace it with a single file. departitionResult(context.getSpark(), resultUrlPartitioned, resultUrl, "txt"); diff --git a/library-api/src/main/java/au/csiro/pathling/library/io/sink/ParquetSink.java b/library-api/src/main/java/au/csiro/pathling/library/io/sink/ParquetSink.java index 3b89c7c1c6..e40ae78430 100644 --- a/library-api/src/main/java/au/csiro/pathling/library/io/sink/ParquetSink.java +++ b/library-api/src/main/java/au/csiro/pathling/library/io/sink/ParquetSink.java @@ -23,6 +23,7 @@ import jakarta.annotation.Nonnull; import org.apache.spark.sql.Dataset; import org.apache.spark.sql.Row; +import org.apache.spark.sql.SaveMode; import org.hl7.fhir.r4.model.Enumerations.ResourceType; /** @@ -36,11 +37,16 @@ public class ParquetSink implements DataSink { @Nonnull private final String path; + @Nonnull + private final SaveMode saveMode; + /** * @param path the path to write the Parquet files to + * @param saveMode the {@link SaveMode} to use */ - public ParquetSink(@Nonnull final String path) { + public ParquetSink(@Nonnull final String path, @Nonnull final SaveMode saveMode) { this.path = path; + this.saveMode = saveMode; } @Override @@ -48,7 +54,7 @@ public void write(@Nonnull final DataSource source) { for (final ResourceType resourceType : source.getResourceTypes()) { final Dataset dataset = source.read(resourceType); final String resultUrl = safelyJoinPaths(path, resourceType.toCode() + ".parquet"); - dataset.write().parquet(resultUrl); + dataset.write().mode(saveMode).parquet(resultUrl); } } diff --git a/library-api/src/test/java/au/csiro/pathling/library/PathlingContextTest.java b/library-api/src/test/java/au/csiro/pathling/library/PathlingContextTest.java index 58d3fc26d4..e382d816b4 100644 --- a/library-api/src/test/java/au/csiro/pathling/library/PathlingContextTest.java +++ b/library-api/src/test/java/au/csiro/pathling/library/PathlingContextTest.java @@ -47,6 +47,7 @@ import au.csiro.pathling.terminology.TerminologyServiceFactory; import ca.uhn.fhir.context.FhirVersionEnum; import jakarta.annotation.Nonnull; +import jakarta.validation.ConstraintViolationException; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -58,7 +59,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.apache.spark.sql.Column; import org.apache.spark.sql.Dataset; diff --git a/library-api/src/test/java/au/csiro/pathling/library/ResourceParserTest.java b/library-api/src/test/java/au/csiro/pathling/library/ResourceParserTest.java index 23fa61aad9..285703c3fc 100644 --- a/library-api/src/test/java/au/csiro/pathling/library/ResourceParserTest.java +++ b/library-api/src/test/java/au/csiro/pathling/library/ResourceParserTest.java @@ -17,9 +17,12 @@ package au.csiro.pathling.library; +import static org.junit.jupiter.api.Assertions.assertEquals; + import ca.uhn.fhir.context.FhirVersionEnum; import com.google.common.base.Charsets; import com.google.common.io.Resources; +import java.io.IOException; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Claim; import org.hl7.fhir.r4.model.Condition; @@ -27,9 +30,6 @@ import org.hl7.fhir.r4.model.Reference; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; public class ResourceParserTest { diff --git a/library-api/src/test/java/au/csiro/pathling/library/io/DataSourcesTest.java b/library-api/src/test/java/au/csiro/pathling/library/io/DataSourcesTest.java index 2f8c71b131..b3cfa2ead5 100644 --- a/library-api/src/test/java/au/csiro/pathling/library/io/DataSourcesTest.java +++ b/library-api/src/test/java/au/csiro/pathling/library/io/DataSourcesTest.java @@ -107,7 +107,7 @@ void ndjsonReadWrite() { queryNdjsonData(data); // Write the data back out to a temporary location. - data.write().ndjson(temporaryDirectory.resolve("ndjson").toString()); + data.write().ndjson(temporaryDirectory.resolve("ndjson").toString(), "error"); // Read the data back in. final QueryableDataSource newData = pathlingContext.read() @@ -151,7 +151,7 @@ void ndjsonReadWriteCustom() { queryNdjsonData(data); // Write the data back out to a temporary location. - data.write().ndjson(temporaryDirectory.resolve("ndjson-custom").toString(), + data.write().ndjson(temporaryDirectory.resolve("ndjson-custom").toString(), "error", (baseName) -> baseName.replaceFirst("Custom", "")); // Read the data back in. @@ -211,7 +211,7 @@ void parquetReadWrite() { queryParquetData(data); // Write the data back out to a temporary location. - data.write().parquet(temporaryDirectory.resolve("parquet").toString()); + data.write().parquet(temporaryDirectory.resolve("parquet").toString(), "error"); // Read the data back in. final QueryableDataSource newData = pathlingContext.read() diff --git a/library-api/src/test/resources/logback.xml b/library-api/src/test/resources/logback.xml new file mode 100644 index 0000000000..4e0d318b6e --- /dev/null +++ b/library-api/src/test/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + + [%level] %logger{36} - %msg%n + + + + + + + + + + diff --git a/library-runtime/pom.xml b/library-runtime/pom.xml index 39d4ad416b..900f641874 100644 --- a/library-runtime/pom.xml +++ b/library-runtime/pom.xml @@ -124,6 +124,13 @@ com/fasterxml/jackson/** + + org/ + ${shaded.dependency.prefix}.org. + + org/hibernate/validator/** + + diff --git a/licenses/apache 2.0 license - license-2.0.html b/licenses/apache 2.0 license - license-2.0.html index 535c113155..15f2c60ac4 100644 --- a/licenses/apache 2.0 license - license-2.0.html +++ b/licenses/apache 2.0 license - license-2.0.html @@ -30,6 +30,7 @@ + - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +} + +#pre-footer .wpcf7 input { + width: 50%; + border-radius: 0; + display:inline-block; + border: 2px black solid; + margin:0; + padding: 15px 20px; +} +#pre-footer .wpcf7 input[type=submit] { + background-color:black; + color:white; +} + +.sidebar-post-loop ul li .wp-block-post-date { + margin-top: 0; +} +.sidebar-post-loop ul li.wp-block-post { + border-bottom : #CECECE 1px solid; + padding-bottom : 10px; + margin-bottom : 10px; + margin-top : 0; +} + +.sidebar-post-loop ul li.wp-block-post:last-child { + border-bottom : none; +} + +.sidebar-post-title.wp-block-post-title { + margin-bottom : 3px !important; + margin-top : 0; + font-family : 'Poppins', sans-serif; +} + +.sidebar-comment-posts { + padding-left : 0; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment { + padding-bottom : 10px; + margin-bottom : 10px; + border-bottom : #CECECE 1px solid; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment:last-child { + border-bottom : none; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta { + font-size : 16px; + line-height : 24px; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta a { + text-decoration : none; + color : #3DA639; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt { + font-size : 16px; + line-height : 28px; + color : #767676; +} + +.sidebar-comment-posts li.wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt p { + margin-bottom : 0; +} + +.sidebar-terms { + padding-left : 0; + margin-left: 0; + list-style : none; +} + +.sidebar-terms li { + border-bottom : 1px solid #CECECE; + padding : 0 0 10px; + margin : 0 0 10px; +} + +.sidebar-terms li:last-child { + border-bottom : none; +} + +.sidebar-terms li a { + text-decoration : none; + color : #3DA639; +} + +.main-post-loop ul li .wp-block-post-featured-image { + border : 1px solid #E1E1E1; +} + +#more-blog-link a { + text-decoration : none; + color : #1D1D1D; +} + +#wp--skip-link--target { + margin-top : 24px !important; +} + +.blog-page--main-post-query .wp-block-post-featured-image { + margin-bottom: 15px; +} + + +.page-id-9688 .content--page .entry-header { + display: none; +} + +.special-sep { + position : relative; +} + +.special-sep:before { + content : ''; + top : 50%; + left : 0; + right : 0; + height : 1px; + content : ''; + position : absolute; + background-color : #000000; +} + +.special-sep:after { + content : ''; + left : calc(50% - 80px / 2); + width : 80px; + content : ''; + position : absolute; + height : 40px; + top : -20px; + background : #FFFFFF url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyNi4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4yIiBiYXNlUHJvZmlsZT0idGlueSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiDQoJIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjU2IDI0OCIgb3ZlcmZsb3c9InZpc2libGUiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQoJPHBhdGggZmlsbD0ibm9uZSIgZD0iTTE0My4zLDE2Ny44YzIyLjEtOC41LDMzLjEtMzMuMiwyNC42LTU1LjNjLTguNS0yMi4xLTMzLjItMzMuMS01NS4zLTI0LjZjLTIyLjEsOC41LTMzLjEsMzMuMi0yNC42LDU1LjMNCgkJYzQuMywxMS4zLDEzLjMsMjAuMywyNC42LDI0LjZMODMuOCwyNDNjLTYzLjUtMjQuNC05NS4zLTk1LjctNzAuOS0xNTkuM3M5NS43LTk1LjMsMTU5LjItNzAuOXM5NS4zLDk1LjcsNzAuOSwxNTkuMw0KCQljLTEyLjUsMzIuNi0zOC4zLDU4LjQtNzAuOSw3MC45TDE0My4zLDE2Ny44Ii8+DQoJPHBhdGggZmlsbD0iIzIzMUYyMCIgZD0iTTE3Mi4yLDI0Ny40Yy0wLjYsMC0xLjItMC4xLTEuOC0wLjRjLTEuMS0wLjUtMS45LTEuNC0yLjMtMi40bC0yOC44LTc1LjFjLTAuOS0yLjMsMC4zLTQuOCwyLjUtNS43DQoJCWM5LjYtMy43LDE3LjEtMTAuOSwyMS4zLTIwLjJjNC4yLTkuNCw0LjQtMTkuOCwwLjgtMjkuNEMxNTYuMiw5NC4zLDEzNCw4NC40LDExNC4yLDkyYy0xOS44LDcuNi0yOS43LDI5LjgtMjIuMSw0OS42DQoJCWMzLjksMTAuMSwxMS45LDE4LjIsMjIuMSwyMi4xYzIuMywwLjksMy40LDMuNCwyLjUsNS43TDg4LDI0NC41Yy0wLjQsMS4xLTEuMywyLTIuMywyLjRjLTEuMSwwLjUtMi4zLDAuNS0zLjQsMC4xDQoJCWMtMzEuOC0xMi4yLTU3LTM2LjEtNzAuOS02Ny4zQy0yLjUsMTQ4LjctMy40LDExNCw4LjgsODIuMXMzNi4xLTU3LDY3LjItNzAuOXM2NS44LTE0LjgsOTcuNy0yLjZzNTcsMzYuMSw3MC45LDY3LjMNCgkJYzEzLjksMzEuMiwxNC44LDY1LjgsMi42LDk3LjdjLTEyLjksMzMuNy0zOS43LDYwLjUtNzMuNCw3My41QzE3My4yLDI0Ny4zLDE3Mi43LDI0Ny40LDE3Mi4yLDI0Ny40TDE3Mi4yLDI0Ny40eiBNMTI4LDguOQ0KCQljLTE2LjQsMC0zMi44LDMuNS00OC4zLDEwLjNjLTI5LDEyLjktNTEuMiwzNi40LTYyLjYsNjZjLTExLjQsMjkuNi0xMC41LDYxLjksMi40LDkwLjljMTIuMywyNy43LDM0LjIsNDkuMiw2MS45LDYxbDI1LjctNjcNCgkJYy0xMC42LTUuMi0xOC45LTE0LjMtMjMuMS0yNS40Yy05LjMtMjQuMywyLjktNTEuNywyNy4xLTYxYzI0LjMtOS4zLDUxLjYsMi45LDYxLDI3LjJjNC41LDExLjgsNC4yLDI0LjYtMC45LDM2LjENCgkJYy00LjUsMTAuMi0xMi4zLDE4LjItMjIuMiwyMy4xbDI1LjcsNjdjMjkuNS0xMi42LDUyLjgtMzYuNyw2NC4zLTY2LjdjMTEuNC0yOS42LDEwLjUtNjEuOS0yLjQtOTAuOWMtMTIuOS0yOS0zNi4zLTUxLjItNjYtNjIuNg0KCQlDMTU2LjgsMTEuNiwxNDIuMyw4LjksMTI4LDguOUwxMjgsOC45eiIvPg0KCTxwYXRoIGZpbGw9IiMyMzFGMjAiIGQ9Ik0yMzcuNywyMjkuNmMtMS42LDEuNi0yLjQsMy42LTIuNCw1LjhjMCwyLjMsMC44LDQuMywyLjQsNS45YzEuNiwxLjYsMy42LDIuNCw1LjksMi40DQoJCWMyLjMsMCw0LjItMC44LDUuOC0yLjRjMS42LTEuNiwyLjQtMy42LDIuNC01LjljMC0yLjItMC44LTQuMi0yLjQtNS44Yy0xLjYtMS43LTMuNi0yLjUtNS45LTIuNQ0KCQlDMjQxLjMsMjI3LjEsMjM5LjQsMjI3LjksMjM3LjcsMjI5LjZMMjM3LjcsMjI5LjZ6IE0yNTAuNywyNDIuNmMtMiwxLjktNC4zLDIuOC03LDIuOGMtMi44LDAtNS4yLTEtNy4xLTIuOQ0KCQljLTEuOS0xLjktMi45LTQuMy0yLjktNy4xYzAtMi45LDEtNS40LDMuMS03LjNjMi0xLjgsNC4zLTIuNyw2LjktMi43YzIuOCwwLDUuMSwxLDcuMSwyLjljMiwyLDIuOSw0LjMsMi45LDcuMQ0KCQlDMjUzLjcsMjM4LjIsMjUyLjcsMjQwLjYsMjUwLjcsMjQyLjZMMjUwLjcsMjQyLjZ6IE0yNDQuNSwyMzJjLTAuNC0wLjItMS0wLjItMS43LTAuMmgtMC43djMuMmgxLjFjMC43LDAsMS4yLTAuMSwxLjYtMC40DQoJCXMwLjYtMC43LDAuNi0xLjNDMjQ1LjQsMjMyLjcsMjQ1LjEsMjMyLjMsMjQ0LjUsMjMyTDI0NC41LDIzMnogTTIzOS4yLDI0MXYtMTEuMWMwLjcsMCwxLjcsMCwzLjEsMHMyLjEsMCwyLjMsMA0KCQljMC45LDAuMSwxLjYsMC4zLDIuMiwwLjZjMSwwLjUsMS41LDEuNCwxLjUsMi43YzAsMC45LTAuMywxLjYtMC44LDJjLTAuNSwwLjQtMS4yLDAuNy0xLjksMC43YzAuNywwLjEsMS4yLDAuNCwxLjYsMC42DQoJCWMwLjcsMC41LDEsMS40LDEsMi41djFjMCwwLjEsMCwwLjIsMCwwLjNzMCwwLjIsMC4xLDAuM2wwLjEsMC4zaC0yLjhjLTAuMS0wLjQtMC4xLTAuOS0wLjItMS41cy0wLjEtMS4xLTAuMi0xLjQNCgkJYy0wLjEtMC40LTAuNC0wLjctMC44LTAuOGMtMC4yLTAuMS0wLjUtMC4xLTEtMC4ybC0wLjYsMGgtMC42djMuOUgyMzkuMnoiLz4NCjwvZz4NCjwvc3ZnPg0K) no-repeat center; +} + +.sidebar .wp-block-latest-posts__list li, +.sidebar .wp-block-latest-comments li { + border-bottom: #CECECE 1px solid; + padding-bottom: 10px; + margin-bottom: 10px; + margin-top: 0; +} +.sidebar .wp-block-latest-posts__post-date, +.sidebar .wp-block-post-date, .wp-block-coblocks-posts__date { + color: #1d1d1d; + font-size: 13px; + font-weight: 400; + margin: 0; +} +.sidebar .wp-block-latest-comments__comment-excerpt p, +.sidebar .wp-block-latest-comments__comment-excerpt, +.sidebar .has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-excerpt, +.sidebar .has-avatars .wp-block-latest-comments__comment .wp-block-latest-comments__comment-meta { + margin: 0; + margin-left:0; + line-height: 28px; + color: #767676; +} + +.blog .sidebar .wp-block-latest-posts__post-title, +.blog .sidebar .wp-block-latest-comments__comment-link, +.blog .sidebar .wp-block-latest-comments__comment-author, +.blog .sidebar .widget_top-posts a, +.blog .sidebar .widget_recent_entries, +.blog .sidebar .widget_categories, +.blog .sidebar #recentcomments { + font-weight: 400; + color: #3da639; + font-size: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.217), 16px); + line-height: 1.8; + text-decoration: none; +} +.blog .sidebar h2 { + font-size: clamp(14px, 0.875rem + ((1vw - 3.2px) * 0.435), 18px); +} + + +.blog .sidebar .wp-block-categories-list, +.blog .sidebar .widget_top-posts ul, +.blog .sidebar .widget_recent_entries, +.blog .sidebar .widget_recent_entries ul, +.blog .sidebar #recentcomments, +.blog .sidebar .widget_categories ul { + padding-left: 0px; +} +.blog .sidebar .wp-block-categories-list li, +.blog .sidebar .widget_top-posts li, +.blog .sidebar .widget_recent_entries li, +.blog .sidebar #recentcomments li, +.blog .sidebar .widget_categories li { + list-style: none; + border-bottom: 1px solid #CECECE; + padding: 0 0 10px; + margin: 0 0 10px; +} - +.blog .sidebar .wp-block-categories-list li a, +.blog .sidebar .widget_recent_entries a, +.blog .sidebar .widget_categories a, +.blog .sidebar #recentcomments a { + text-decoration: none; + cursor: pointer !important; + line-height: 1.8; + font-weight: 400; + color: #3da639; +} + +#comments ul.reaction-list { + list-style-type: none; +} +#comments ul.reaction-list li { + display:inline-block; + padding:0; + margin:0 +} +#comments ul.reaction-list li .hide-name, +#comments ul.reaction-list li .emoji-overlay { + display:none; +} +#comments ul.reaction-list li img { + width:50px; + max-width: auto; +} +#comments ul.reaction-list li a.customize-unpreviewable { + cursor:pointer !important; + display:inline-block; +} +.comment-body { + width: auto; +} +.email-block-wrap { + display:block; + width:100%; + clear:both; + margin-bottom: -5em; + z-index: 0; + position: relative; +} +.email-block-wrap input { + width: 50%; + display:inline-block; + border-radius: 0; + border: 2px black solid; + height: 60px; +} +.email-block-wrap span.wpcf7-not-valid-tip { + position: absolute; + color:white +} +.email-block-wrap span.wpcf7-form-control-wrap { + display: inline-block; + width:50%; +} +.email-block-wrap span.wpcf7-form-control-wrap input { + width:100%; +} +.email-block-wrap input[type=submit] { + background: black; + color:white; +} +.email-block-wrap input[type=submit]:hover { + background: white; + color: black; +} +.email-block-wrap input:hover, +.email-block-wrap form.customize-unpreviewable input:hover, +form.customize-unpreviewable { + cursor:pointer !important; +} +.email-block-wrap input[type=email]:hover, +.email-block-wrap form.customize-unpreviewable input[type=email]:hover { + cursor:text !important; +} +.email-block-wrap .wpcf7-response-output { + border-color:white !important; + margin:10px 0 !important; + color:white; +} +.email-block-wrap form p { + margin: 0; + padding: 0; +} +.email-block-wrap { + z-index: 1; + border-bottom: 1px solid black; +} +.footer-cta { + z-index: 2; + position: relative; +} +.footer-main { + padding-top: 50px +} +.blog .first-post .post--byline { + font-size:clamp(14px, 0.875rem + (1vw - 3.2px) * 0.217, 16px); + color: gray; +} +.blog .first-post .entry-header ul { + list-style-type: none; + padding-left: 0; + margin-left: 0; + font-size: clamp(14px, 0.875rem + (1vw - 3.2px) * 0.217, 16px); +} + +.blog .first-post h2 { + font-size: 35px; + line-height: 45px; +} + +.blog .content .content-full .content--page { + max-width: 1550px; +} +.syndication-links { + margin-top: 0; +} +@media only screen and (min-width: 600px) { + .archive-columns { + gap: 4%; + } +} +@media only screen and (min-width: 782px) { + .wp-block-column.two-column { + max-width: 46%; + min-width: 46%; + } +} +.blog .post-archive-wrap .wp-block-column h2, +.archive h2.post--title.entry-title { + margin-top: 35px; + margin-bottom: 35px; + line-height: 37px; +} +.archive .entry-meta.post--byline a { + color: #767676; + font-size: clamp(14px, 0.875rem + (1vw - 3.2px) * 0.217, 16px); +} +.error-404 label, +.widget_top-posts .widget-inner > p { + display:none; +} + +.archive .archive-title.page--title { + margin-bottom: 0.75rem !important; +} + +.archive-description { + padding-bottom: 2.75rem; +} + +.archive-description p { + margin: 0; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -619,6 +905,7 @@

The MIT License

@@ -628,40 +915,52 @@

The MIT License

@@ -669,9 +968,11 @@

The MIT License

+ + + - - - - - + + - - - + + + + + + + - - - + + - - - + + + diff --git a/licenses/mit - mit.html b/licenses/mit - mit.html new file mode 100644 index 0000000000..984193d015 --- /dev/null +++ b/licenses/mit - mit.html @@ -0,0 +1,1176 @@ + + + + + + + + The MIT License – Open Source Initiative + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + +

Search Our Site

+ +
+

Search

+
+ +
+ +
+ +
+ +
+
+ +
+
+
+
+
+
+ + + +

The MIT License

+ Version N/A + SPDX short identifier: + MIT +

+
+
+ Open Source Initiative Approved License +
+
+
+
+
+ +
+
+

Copyright <YEAR> <COPYRIGHT HOLDER>

+
+
+

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

+

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+
+
+ +
+
+
+ + +
+ + + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/licenses/public domain - license.html b/licenses/public domain - license.html index e2363868fc..0b2b7de972 100644 --- a/licenses/public domain - license.html +++ b/licenses/public domain - license.html @@ -5,7 +5,14 @@ - + + + @@ -20,105 +27,110 @@ - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + JSON-java/LICENSE at master · stleary/JSON-java · GitHub - + + + - + - + - - - - - + @@ -129,58 +141,61 @@ - - + + + + + - - + + - + + - - - - + + + + - - + + + - - - + @@ -198,11 +213,34 @@
- Skip to content + Skip to content + + + + + + + + + + + + + + + + +
+
+ + @@ -210,10 +248,11 @@ - - - + +
@@ -261,7 +300,7 @@