From a2a0a8a66231a4ba8344d75ba28887a79577cda3 Mon Sep 17 00:00:00 2001 From: longma1 <32119004+longma1@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:30:32 -0700 Subject: [PATCH 01/24] pom bump to 7.0.0 (#5615) * pom bump to 7.0.0 * add version enum * fixed feb release name --------- Co-authored-by: Long Ma --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../src/main/java/ca/uhn/fhir/util/VersionEnum.java | 5 ++++- hapi-fhir-bom/pom.xml | 4 ++-- hapi-fhir-checkstyle/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-api/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- hapi-fhir-converter/pom.xml | 2 +- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- .../changelog/7_0_0/5617-fix-default-partition-setting.yaml | 5 +++++ .../resources/ca/uhn/hapi/fhir/changelog/7_0_0/version.yaml | 2 +- hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- .../java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java | 3 ++- hapi-fhir-jpaserver-elastic-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-hfql/pom.xml | 2 +- hapi-fhir-jpaserver-ips/pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- .../main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-cds-hooks/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java | 6 ++++++ .../ca/uhn/fhir/rest/server/provider/ProviderConstants.java | 2 ++ hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml | 2 +- hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml | 4 ++-- hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml | 2 +- hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml | 2 +- hapi-fhir-serviceloaders/pom.xml | 2 +- .../hapi-fhir-spring-boot-autoconfigure/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-client-apache/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-client-okhttp/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-server-jersey/pom.xml | 2 +- hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml | 2 +- hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-sql-migrate/pom.xml | 2 +- hapi-fhir-storage-batch2-jobs/pom.xml | 2 +- hapi-fhir-storage-batch2-test-utilities/pom.xml | 2 +- hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-cr/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 2 +- hapi-fhir-structures-r5/pom.xml | 2 +- hapi-fhir-test-utilities/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2.1/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation-resources-r4b/pom.xml | 2 +- hapi-fhir-validation-resources-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml | 2 +- tests/hapi-fhir-base-test-mindeps-client/pom.xml | 2 +- tests/hapi-fhir-base-test-mindeps-server/pom.xml | 2 +- 84 files changed, 100 insertions(+), 83 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5617-fix-default-partition-setting.yaml diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index dec1bbf3cda3..23def8db8d42 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 233cd5538750..74b94af7cc2d 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 830c0e393f8e..c5fdfd45ac87 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java index 791401008c3b..abb861c91c05 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java @@ -134,7 +134,10 @@ public enum VersionEnum { V6_10_3, V6_11_0, - V7_0_0; + V7_0_0, + + V7_1_0, + V7_2_0; public static VersionEnum latestVersion() { VersionEnum[] values = VersionEnum.values(); diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index a7c8dcd61a69..8e9ef9275d7d 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 6e2c78e46cb0..5edd95c36b73 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index fc75658ef53a..70c272893786 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 39eff7fbcdde..331c7d180e3b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 1ce5e5d7c18d..03500e4520fa 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 277a861117b3..14529d678290 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 31ca234fadfd..0a512023af91 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index cf55ea1c828e..98ef7fa89925 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 80eae680de3b..38fac62b89fe 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 8190f9e22ff3..909784c72fb0 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5617-fix-default-partition-setting.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5617-fix-default-partition-setting.yaml new file mode 100644 index 000000000000..ce2a36edf711 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5617-fix-default-partition-setting.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5617 +title: "Resource UserData RESOURCE_PARTITION_ID was incorrectly being set to null for the default partition. +This has been corrected to use RequestPartitionId.defaultPartition()" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/version.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/version.yaml index 08d30a1acef5..ddc0f7d4b84c 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/version.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/version.yaml @@ -1,3 +1,3 @@ --- release-date: "2023-02-18" -codename: "TBD" +codename: "Apollo" diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 6314ad868af7..8559dc4129e9 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 6997b0d63fbf..1675f275910e 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index 9e3137bc918e..9fa97dcb3f89 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index fb770122e827..84f549d2412a 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java index ef2b3ca6be2b..5bcce1c0c0ef 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java @@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; @@ -248,7 +249,7 @@ private void populateResourcePartitionInformation( myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId()); retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId()); } else { - retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null); + retVal.setUserData(Constants.RESOURCE_PARTITION_ID, RequestPartitionId.defaultPartition()); } } } diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 1657fc945d67..29047652bab9 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index 91246c043155..5e10c3a8abb7 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index c586c8842d33..f5581fc6190f 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 34b6e0d86d9d..d0101f627047 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 9b3da9117eaa..6fd6ea4f5920 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 27a36dd1fc9c..2dd2c0ab55c9 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -263,7 +263,7 @@ public class JpaConstants { /** * The name of the default partition */ - public static final String DEFAULT_PARTITION_NAME = "DEFAULT"; + public static final String DEFAULT_PARTITION_NAME = ProviderConstants.DEFAULT_PARTITION_NAME; /** * The name of the collection of all partitions diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 5df87e82fd7f..f2bcfaec463c 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 535df24d3e71..9c324951d12a 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index 7e4984179a72..13b2838847ff 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index 24c196dc8eea..e7f3bbf68abb 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 854de84c4a21..05afff797cdf 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index 31b54f808d09..f00ff0251f14 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 2fdaf7dafd15..0aac452bcd16 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index e3a0b3efc8cf..a81d904eba5c 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 5113e3c06266..01b61f2c55ae 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index ed320ecad095..eb0d99602936 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 43b3d4db3ce7..285f0c5e358e 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 4c4ea01bd632..8bc4912e8b51 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index ef697cd2752a..a4f9b7f42617 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java index 665b7bbecdec..9316cd89d35a 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleBuilder.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import com.google.common.collect.Lists; import jakarta.annotation.Nonnull; import org.apache.commons.lang3.Validate; @@ -234,6 +235,11 @@ private boolean matchesResource(IBaseResource theResource) { RequestPartitionId partitionId = (RequestPartitionId) theResource.getUserData(Constants.RESOURCE_PARTITION_ID); if (partitionId != null) { + if (partitionId.hasDefaultPartitionId() + && myTenantIds.contains(ProviderConstants.DEFAULT_PARTITION_NAME)) { + return myOutcome; + } + String partitionNameOrNull = partitionId.getFirstPartitionNameOrNull(); if (partitionNameOrNull == null || !myTenantIds.contains(partitionNameOrNull)) { return !myOutcome; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java index bd8ee9640930..1d9e7f2d90f2 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/provider/ProviderConstants.java @@ -52,6 +52,8 @@ public class ProviderConstants { public static final String PARTITION_MANAGEMENT_PARTITION_NAME = "name"; public static final String PARTITION_MANAGEMENT_PARTITION_DESC = "description"; + public static final String DEFAULT_PARTITION_NAME = "DEFAULT"; + /** * Operation name: diff */ diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index 52cdaf3c29ba..43551b1c2dd1 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index 216873cd23c7..de1719b06ee7 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index a5a19274d2d5..d9365b831482 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index 2c7dda6e262d..69da3ffa5466 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index 4b4743e797cc..fa98b63e3b02 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 280244179520..29d7cee14c89 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index ce85e8237d49..76e89cd70516 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index 94826df1f1d8..2abfa034942b 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 846c376ea2f1..23d33465acea 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 9f1a81f9ba5c..9090342c16ee 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index c1defd308e86..638fb2de9f35 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index ef51abf6298a..059bc9dad25b 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index bb490eedb596..26ee701074ef 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index 9f887a0055eb..c3f40aacd575 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index e619bfd89cf1..8c4c1faaf01c 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 43d24771f7bd..7a95ec517728 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index 9668284c6138..4381648826b2 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index 3532dbbd9557..c37dbd5e27cd 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index 194eb269d0f0..2895bd7588e6 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index 5d61d558ebe8..860fd7343653 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 9691c8c647b2..4e03f59d5e9f 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index 5a9c7289893e..c93bc03b902a 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index e82ea20c474a..870beb10cebf 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index 3862f6bfc918..cab349ea820d 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index ad81935e5481..1b423677dc58 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index f62d907c6f2c..17dbd4743ce3 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 5ac839d147ef..8a70b452519b 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 0187b643a2f2..8e04fa95f365 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 3e679c1c08ec..0ed01e9878d7 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 1d35f40452e1..8742b563b7be 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index f66691a5f81e..0ef7ba05a1da 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 5148f0a3c20e..d315c462cce8 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 6327b9b84579..f2c8f8042316 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index 9965204da32d..d8d271f67507 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 8dbce2490ef7..29c27cf38254 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 4c471109d241..0a14f1c92115 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 2ae0bc8c323f..6df1a2de51e8 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 2a5d2aeef800..bf20a185b7b3 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 10f3b74cd68c..0564e9040dbe 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 33a92e0e12b7..c621b1701b78 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index e7b8c1b886e3..566c887a1d4a 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index c9ed824c9094..c07a5ffff406 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.11.10-SNAPSHOT + 7.0.0-SNAPSHOT ../../pom.xml From b982abbfbe33469a9445ec9bf088817a99e22719 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Tue, 23 Jan 2024 17:56:26 -0800 Subject: [PATCH 02/24] Check index existence on raw SQL creation (#5624) * Add conditional check on index existence before we try again * Add conditional check on index existence before we try again * Changelog * remit * remit * debug statements --- .../changelog/7_0_0/5547-collation-index-fix.yaml | 5 +++++ .../jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java | 11 ++++++++--- .../ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5547-collation-index-fix.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5547-collation-index-fix.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5547-collation-index-fix.yaml new file mode 100644 index 000000000000..c6ab2f025562 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5547-collation-index-fix.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5547 +title: "The addition of the indexes `idx_sp_uri_hash_identity_pattern_ops` and `idx_sp_string_hash_nrm_pattern_ops` could occasionally timeout during migration in Postgresql on large databases, leaving the migration table in a failed state, and Smile CDR unable to boot. +Now existence of the index is checked before attempting to add it again." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 9d50199fb88d..636b436746ea 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -184,8 +184,10 @@ protected void init700() { QUERY_FOR_COLUMN_COLLATION_TEMPLATE, "HFJ_SPIDX_STRING".toLowerCase(), "SP_VALUE_NORMALIZED".toLowerCase()), - "Column HFJ_SPIDX_STRING.SP_VALUE_NORMALIZED already has a collation of 'C' so doing nothing"); - + "Column HFJ_SPIDX_STRING.SP_VALUE_NORMALIZED already has a collation of 'C' so doing nothing") + .onlyIf( + "SELECT NOT EXISTS(select 1 from pg_indexes where indexname='idx_sp_string_hash_nrm_pattern_ops')", + "Index idx_sp_string_hash_nrm_pattern_ops already exists"); version.executeRawSql( "20231212.2", "CREATE UNIQUE INDEX idx_sp_uri_hash_identity_pattern_ops ON public.hfj_spidx_uri USING btree (hash_identity, sp_uri varchar_pattern_ops, res_id, partition_id)") @@ -195,7 +197,10 @@ protected void init700() { QUERY_FOR_COLUMN_COLLATION_TEMPLATE, "HFJ_SPIDX_URI".toLowerCase(), "SP_URI".toLowerCase()), - "Column HFJ_SPIDX_STRING.SP_VALUE_NORMALIZED already has a collation of 'C' so doing nothing"); + "Column HFJ_SPIDX_STRING.SP_VALUE_NORMALIZED already has a collation of 'C' so doing nothing") + .onlyIf( + "SELECT NOT EXISTS(select 1 from pg_indexes where indexname='idx_sp_uri_hash_identity_pattern_ops')", + "Index idx_sp_uri_hash_identity_pattern_ops already exists."); } // This fix was bad for MSSQL, it has been set to do nothing. diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java index 3f47dc4606d5..39365829c775 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java @@ -597,11 +597,11 @@ public BuilderCompleteTask onlyIf(@Language("SQL") String theSql, String reason) "Only SELECT statements (including CTEs) are allowed here. Please check your SQL: [%s]", theSql)); } - ourLog.info("SQL to evaluate: {}", theSql); + ourLog.debug("SQL to evaluate: {}", theSql); myTask.addPrecondition(new ExecuteTaskPrecondition( () -> { - ourLog.info("Checking precondition for SQL: {}", theSql); + ourLog.debug("Checking precondition for SQL: {}", theSql); return MigrationJdbcUtils.queryForSingleBooleanResultMultipleThrowsException( theSql, myTask.newJdbcTemplate()); }, From 47867fc628ebe881fe9ceb72f3e7e6ca14ef359a Mon Sep 17 00:00:00 2001 From: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Date: Wed, 24 Jan 2024 07:40:21 -0500 Subject: [PATCH 03/24] 5621 deadlock on caffeine cache when creating a resource with conditional create (#5622) * Modifying the CacheProvider to avoid doing db I/O within the cache miss value supplier callback. * Setting the initial capacity of instantiated caches to the cache max size to avoid resizing during operations. * adding changelog and spotless. * Fixing typo. * Addressing comments from code review. --------- Co-authored-by: peartree --- ...-on-caffeine-cash-on-conditional-create.yaml | 4 ++++ .../uhn/fhir/jpa/dao/index/IdHelperService.java | 17 +++++++++++++---- .../fhir/sl/cache/caffeine/CacheProvider.java | 6 ++++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5621-fix-potential-deadlock-on-caffeine-cash-on-conditional-create.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5621-fix-potential-deadlock-on-caffeine-cash-on-conditional-create.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5621-fix-potential-deadlock-on-caffeine-cash-on-conditional-create.yaml new file mode 100644 index 000000000000..5fb4b901ffe5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5621-fix-potential-deadlock-on-caffeine-cash-on-conditional-create.yaml @@ -0,0 +1,4 @@ +--- +type: fix +issue: 5621 +title: "Fixed a deadlock in resource conditional create." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index b21c64dba93f..93c0de299e3f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -477,10 +477,19 @@ public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceTy @Override public Optional translatePidIdToForcedIdWithCache(JpaPid theId) { - return myMemoryCacheService.get( - MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, - theId.getId(), - pid -> myResourceTableDao.findById(pid).map(ResourceTable::asTypedFhirResourceId)); + // do getIfPresent and then put to avoid doing I/O inside the cache. + Optional forcedId = + myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theId.getId()); + + if (forcedId == null) { + // This is only called when we know the resource exists. + // So this optional is only empty when there is no hfj_forced_id table + // note: this is obsolete with the new fhir_id column, and will go away. + forcedId = myResourceTableDao.findById(theId.getId()).map(ResourceTable::asTypedFhirResourceId); + myMemoryCacheService.put(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theId.getId(), forcedId); + } + + return forcedId; } private ListMultimap organizeIdsByResourceType(Collection theIds) { diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/src/main/java/ca/uhn/fhir/sl/cache/caffeine/CacheProvider.java b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/src/main/java/ca/uhn/fhir/sl/cache/caffeine/CacheProvider.java index 6a1376410f0c..cc324964f023 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/src/main/java/ca/uhn/fhir/sl/cache/caffeine/CacheProvider.java +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/src/main/java/ca/uhn/fhir/sl/cache/caffeine/CacheProvider.java @@ -44,6 +44,9 @@ public LoadingCache create(long timeoutMillis, CacheLoader loading) public Cache create(long timeoutMillis, long maximumSize) { return new CacheDelegator(Caffeine.newBuilder() .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) + // Caffeine locks the whole array when growing the hash table. + // Set initial capacity to max to avoid this. All our caches are <1M entries. + .initialCapacity((int) maximumSize) .maximumSize(maximumSize) .build()); } @@ -51,6 +54,9 @@ public Cache create(long timeoutMillis, long maximumSize) { public LoadingCache create(long timeoutMillis, long maximumSize, CacheLoader loading) { return new LoadingCacheDelegator(Caffeine.newBuilder() .expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS) + // Caffeine locks the whole array when growing the hash table. + // Set initial capacity to max to avoid this. All our caches are <1M entries. + .initialCapacity((int) maximumSize) .maximumSize(maximumSize) .build(loading::load)); } From 4b1ac778ccd387c897635d8bc7b65425c4952840 Mon Sep 17 00:00:00 2001 From: Nathan Doef Date: Wed, 24 Jan 2024 10:35:40 -0500 Subject: [PATCH 04/24] Searching with more than one chained Bundle SearchParameter returns incorrect results (#5614) * Failing test * fix * changelog --- ...ndle-composition-searchparameters-fix.yaml | 5 ++ .../fhir/jpa/search/builder/QueryStack.java | 2 +- .../fhir/jpa/dao/r4/ChainingR4SearchTest.java | 80 ++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5623-searching-with-multiple-bundle-composition-searchparameters-fix.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5623-searching-with-multiple-bundle-composition-searchparameters-fix.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5623-searching-with-multiple-bundle-composition-searchparameters-fix.yaml new file mode 100644 index 000000000000..1041359ccf0e --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5623-searching-with-multiple-bundle-composition-searchparameters-fix.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5623 +title: "Previously, searches that used more than one chained `Bundle` `SearchParameter` (i.e. `Composition`) were only +adding one condition to the underlying SQL query which resulted in incorrect search results. This has been fixed." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java index 673e239ae8d4..bdd2d5c9186d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java @@ -2466,7 +2466,7 @@ private Condition createPredicateSearchParameter( theRequestPartitionId, andPredicates, nextAnd)) { - break; + continue; } EmbeddedChainedSearchModeEnum embeddedChainedSearchModeEnum = diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java index 31fecd59bddc..7c75a50ae87d 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java @@ -11,14 +11,18 @@ import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.AuditEvent; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Composition; import org.hl7.fhir.r4.model.Device; import org.hl7.fhir.r4.model.DomainResource; import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Location; import org.hl7.fhir.r4.model.MessageHeader; @@ -27,22 +31,26 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; import org.springframework.beans.factory.annotation.Autowired; import java.io.IOException; +import java.sql.Date; import java.util.ArrayList; import java.util.List; import static org.apache.commons.lang3.StringUtils.countMatches; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.in; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; @@ -1573,6 +1581,76 @@ public void testQueryStructure() throws Exception { countUnionStatementsInGeneratedQuery("/Observation?subject:Location.name=Smith", 1); } + @ParameterizedTest + @CsvSource({ + // search url expected count + "/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=1980-01-01, 1", // correct identifier, correct birthdate + "/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-1, 1", // correct birthdate, correct identifier + "/Bundle?composition.patient.identifier=system|value-1&composition.patient.birthdate=2000-01-01, 0", // correct identifier, incorrect birthdate + "/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-1, 0", // incorrect birthdate, correct identifier + "/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=1980-01-01, 0", // incorrect identifier, correct birthdate + "/Bundle?composition.patient.birthdate=1980-01-01&composition.patient.identifier=system|value-2, 0", // correct birthdate, incorrect identifier + "/Bundle?composition.patient.identifier=system|value-2&composition.patient.birthdate=2000-01-01, 0", // incorrect identifier, incorrect birthdate + "/Bundle?composition.patient.birthdate=2000-01-01&composition.patient.identifier=system|value-2, 0", // incorrect birthdate, incorrect identifier + }) + public void testMultipleChainedBundleCompositionSearchParameters(String theSearchUrl, int theExpectedCount) { + createSearchParameter("bundle-composition-patient-birthdate", + "composition.patient.birthdate", + "Bundle", + "Bundle.entry.resource.ofType(Patient).birthDate", + Enumerations.SearchParamType.DATE + ); + + createSearchParameter("bundle-composition-patient-identifier", + "composition.patient.identifier", + "Bundle", + "Bundle.entry.resource.ofType(Patient).identifier", + Enumerations.SearchParamType.TOKEN + ); + + createDocumentBundleWithPatientDetails("1980-01-01", "system", "value-1"); + + SearchParameterMap params = myMatchUrlService.getResourceSearch(theSearchUrl).getSearchParameterMap().setLoadSynchronous(true); + assertSearchReturns(myBundleDao, params, theExpectedCount); + } + + private void createSearchParameter(String theId, String theCode, String theBase, String theExpression, Enumerations.SearchParamType theType) { + SearchParameter searchParameter = new SearchParameter(); + searchParameter.setId(theId); + searchParameter.setCode(theCode); + searchParameter.setName(theCode); + searchParameter.setUrl("http://example.org/SearchParameter/" + theId); + searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); + searchParameter.addBase(theBase); + searchParameter.setType(theType); + searchParameter.setExpression(theExpression); + searchParameter = (SearchParameter) mySearchParameterDao.update(searchParameter, mySrd).getResource(); + mySearchParamRegistry.forceRefresh(); + assertNotNull(mySearchParamRegistry.getActiveSearchParam(theBase, searchParameter.getName())); + } + + private void createDocumentBundleWithPatientDetails(String theBirthDate, String theIdentifierSystem, String theIdentifierValue) { + Patient patient = new Patient(); + patient.setBirthDate(Date.valueOf(theBirthDate)); + patient.addIdentifier().setSystem(theIdentifierSystem).setValue(theIdentifierValue); + patient = (Patient) myPatientDao.create(patient, mySrd).getResource(); + assertSearchReturns(myPatientDao, SearchParameterMap.newSynchronous(), 1); + + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.DOCUMENT); + Composition composition = new Composition(); + composition.setType(new CodeableConcept().addCoding(new Coding().setCode("code").setSystem("http://example.org"))); + bundle.addEntry().setResource(composition); + composition.getSubject().setReference(patient.getIdElement().getValue()); + bundle.addEntry().setResource(patient); + myBundleDao.create(bundle, mySrd); + assertSearchReturns(myBundleDao, SearchParameterMap.newSynchronous(), 1); + } + + private void assertSearchReturns(IFhirResourceDao theDao, SearchParameterMap theSearchParams, int theExpectedCount){ + assertEquals(theExpectedCount, theDao.search(theSearchParams, mySrd).size()); + } + private void countUnionStatementsInGeneratedQuery(String theUrl, int theExpectedNumberOfUnions) throws IOException { myCaptureQueriesListener.clear(); searchAndReturnUnqualifiedVersionlessIdValues(theUrl); From ba28ff977f8f322ceaa9624aa8e59229e1ef5a6d Mon Sep 17 00:00:00 2001 From: Etienne Poirier <33007955+epeartree@users.noreply.github.com> Date: Thu, 25 Jan 2024 11:18:44 -0500 Subject: [PATCH 05/24] Avoiding Exception being thrown on @EventListener invocation (#5628) * replaced EventListener annotation with @PreDestroy * adding changelog --------- Co-authored-by: peartree --- ...5626-fix-potential-exception-thrown-on-eventlistener.yaml | 5 +++++ .../java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5626-fix-potential-exception-thrown-on-eventlistener.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5626-fix-potential-exception-thrown-on-eventlistener.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5626-fix-potential-exception-thrown-on-eventlistener.yaml new file mode 100644 index 000000000000..88e38416824a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5626-fix-potential-exception-thrown-on-eventlistener.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5626 +title: "Previously, an exception could be thrown by the container when executing a contextClosedEvent on the + Scheduler Service. This issue has been fixed." diff --git a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java index 9d59f8238d35..358aa408176a 100644 --- a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java +++ b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/sched/BaseSchedulerServiceImpl.java @@ -28,13 +28,13 @@ import ca.uhn.fhir.util.StopWatch; import com.google.common.annotations.VisibleForTesting; import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; import org.quartz.JobKey; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; -import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.context.event.EventListener; import org.springframework.core.env.Environment; @@ -177,7 +177,7 @@ private void scheduleJobs() { values.forEach(t -> t.scheduleJobs(this)); } - @EventListener(ContextClosedEvent.class) + @PreDestroy public void stop() { ourLog.info("Shutting down task scheduler..."); From 1441a90a9fba4ddd3eaf10fc3bd962c998e9b454 Mon Sep 17 00:00:00 2001 From: longma1 <32119004+longma1@users.noreply.github.com> Date: Thu, 25 Jan 2024 09:48:29 -0700 Subject: [PATCH 06/24] simple fix (#5630) Co-authored-by: Long Ma --- .../java/ca/uhn/fhir/util/AttachmentUtil.java | 5 +++-- ...5537-attachment-util-content-type-fix.yaml | 5 +++++ .../fhir/validator/AttachmentUtilTest.java | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5537-attachment-util-content-type-fix.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java index 783d71682f5e..cee970715afd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/AttachmentUtil.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.context.BaseRuntimeElementDefinition; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.primitive.CodeDt; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.ICompositeType; @@ -41,8 +42,8 @@ public static IPrimitiveType getOrCreateData(FhirContext theContext, ICo return getOrCreateChild(theContext, theAttachment, "data", "base64Binary"); } - public static IPrimitiveType getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) { - return getOrCreateChild(theContext, theAttachment, "contentType", "string"); + public static IPrimitiveType getOrCreateContentType(FhirContext theContext, ICompositeType theAttachment) { + return getOrCreateChild(theContext, theAttachment, "contentType", "code"); } public static IPrimitiveType getOrCreateUrl(FhirContext theContext, ICompositeType theAttachment) { diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5537-attachment-util-content-type-fix.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5537-attachment-util-content-type-fix.yaml new file mode 100644 index 000000000000..6a72835bcc00 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5537-attachment-util-content-type-fix.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5537 +title: "Calling the method getOrCreateContentType in AttachmentUtil on an attachment with no content type would throw exception because contentType is a code not a string. +This fixes the function to create an empty code as expected" diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java index 072b1a5d6b28..fa6220673215 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/validator/AttachmentUtilTest.java @@ -1,11 +1,16 @@ package ca.uhn.fhir.validator; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.util.AttachmentUtil; import org.hl7.fhir.instance.model.api.ICompositeType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Attachment; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; public class AttachmentUtilTest { @@ -53,4 +58,18 @@ public void testCreateAttachmentR5() { String encoded = ctx.newJsonParser().encodeResourceToString(communication); assertEquals("{\"resourceType\":\"Communication\",\"payload\":[{\"contentAttachment\":{\"contentType\":\"text/plain\",\"data\":\"AAECAw==\",\"url\":\"http://foo\",\"size\":123}}]}", encoded); } + + @Test + public void testGetOrCreateContentTypeOnEmptyAttachmentR4(){ + FhirContext ctx = FhirContext.forR4Cached(); + Attachment attachment = (Attachment) AttachmentUtil.newInstance(ctx); + + assertNull(attachment.getContentType()); + + IPrimitiveType contentType = AttachmentUtil.getOrCreateContentType(ctx, attachment); + + contentType.setValueAsString("text/plain"); + + assertNotNull(attachment.getContentType()); + } } From 224e56931763bd9dd679144557b5b5a09dc7541f Mon Sep 17 00:00:00 2001 From: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com> Date: Fri, 26 Jan 2024 08:49:12 -0700 Subject: [PATCH 07/24] Incorrect version of auto versioned reference for conditional update with urn id placeholder (#5625) * Incorrect version from versioned_references.auto_at_paths for conditional update - implementation --- ...onditional-update-with-id-placeholder.yaml | 7 ++ .../r4/FhirResourceDaoR4QueryCountTest.java | 4 +- ...irResourceDaoR4VersionedReferenceTest.java | 109 +++++++++++++----- .../jpa/dao/BaseTransactionProcessor.java | 5 +- .../uhn/fhir/jpa/dao/IdSubstitutionMap.java | 17 +++ .../fhir/jpa/dao/IdSubstitutionMapTest.java | 68 +++++++++++ 6 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5619-fix-auto-version-reference-for-conditional-update-with-id-placeholder.yaml create mode 100644 hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMapTest.java diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5619-fix-auto-version-reference-for-conditional-update-with-id-placeholder.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5619-fix-auto-version-reference-for-conditional-update-with-id-placeholder.yaml new file mode 100644 index 000000000000..3077b0e9595a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5619-fix-auto-version-reference-for-conditional-update-with-id-placeholder.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 5619 +jira: SMILE-7909 +title: "Previously, when a transaction was posted with a resource that had placeholder references and auto versioning +references enabled for that path, if the target resource was included in the Bundle but not modified, the reference was +saved with a version number that didn't exist. This has been fixed." diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index afbd4fff725b..33fdc8269800 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -3379,7 +3379,7 @@ public void testMassIngestionMode_TransactionWithChanges() { assertEquals(8, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); myCaptureQueriesListener.logInsertQueries(); assertEquals(4, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); - assertEquals(7, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(6, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); @@ -3462,7 +3462,7 @@ public void testMassIngestionMode_TransactionWithChanges_NonVersionedTags() thro myCaptureQueriesListener.logSelectQueriesForCurrentThread(); assertEquals(8, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(2, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); - assertEquals(6, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); + assertEquals(5, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java index 49a714afd358..da64fee8df50 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java @@ -380,17 +380,11 @@ public void testInsertVersionedReferenceAtPath_InTransaction_TargetConditionalCr observation.getEncounter().setReference(encounter.getId()); // not versioned builder.addTransactionCreateEntry(observation); - Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle()); - ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); - assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus()); - assertEquals("200 OK", outcome.getEntry().get(1).getResponse().getStatus()); - assertEquals("201 Created", outcome.getEntry().get(2).getResponse().getStatus()); + Bundle outcome = createAndValidateBundle((Bundle) builder.getBundle(), + List.of("200 OK", "200 OK", "201 Created"), List.of("2", "1", "1")); IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation()); - assertEquals("2", patientId.getVersionIdPart()); - assertEquals("1", encounterId.getVersionIdPart()); - assertEquals("1", observationId.getVersionIdPart()); // Read back and verify that reference is now versioned observation = myObservationDao.read(observationId); @@ -429,14 +423,10 @@ public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() { builder.addTransactionCreateEntry(observation); myCaptureQueriesListener.clear(); - Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle()); - ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); - assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus()); - assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus()); + Bundle outcome = createAndValidateBundle((Bundle) builder.getBundle(), + List.of("200 OK", "201 Created"), List.of("3", "1")); IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); - assertEquals("3", patientId.getVersionIdPart()); - assertEquals("1", observationId.getVersionIdPart()); // Make sure we're not introducing any extra DB operations assertEquals(3, myCaptureQueriesListener.logSelectQueries().size()); @@ -468,14 +458,10 @@ public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditi myCaptureQueriesListener.clear(); - Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle()); - ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); - assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus()); - assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus()); + Bundle outcome = createAndValidateBundle((Bundle) builder.getBundle(), + List.of("200 OK", "201 Created"), List.of("3", "1")); IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); - assertEquals("3", patientId.getVersionIdPart()); - assertEquals("1", observationId.getVersionIdPart()); // Make sure we're not introducing any extra DB operations assertEquals(4, myCaptureQueriesListener.logSelectQueries().size()); @@ -563,20 +549,91 @@ public void testInsertVersionedReferencesByPath_resourceReferenceNotInTransactio BundleBuilder builder = new BundleBuilder(myFhirContext); builder.addTransactionCreateEntry(messageHeader); - ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(builder.getBundle())); - Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle()); - ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); - assertEquals("201 Created", outcome.getEntry().get(0).getResponse().getStatus()); - + Bundle outcome = createAndValidateBundle((Bundle) builder.getBundle(), + List.of("201 Created"), List.of("1")); IdType messageHeaderId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); assertEquals("2", patient.getIdElement().getVersionIdPart()); - assertEquals("1", messageHeaderId.getVersionIdPart()); // read back and verify that reference is versioned messageHeader = myMessageHeaderDao.read(messageHeaderId); assertEquals(patient.getIdElement().getValue(), messageHeader.getFocus().get(0).getReference()); } + @Test + @DisplayName("#5619 Incorrect version of auto versioned reference for conditional update with urn id placeholder") + public void testInsertVersionedReferencesByPath_conditionalUpdateNoOpInTransaction_addsCorrectVersionToReference() { + Supplier supplier = () -> { + // create patient + Patient patient = new Patient(); + patient.setActive(true); + patient.addIdentifier().setSystem("http://example.com").setValue("test"); + + // add patient to the Bundle - conditional update with placeholder url + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.TRANSACTION); + bundle.addEntry() + .setResource(patient) + .setFullUrl("urn:uuid:00001") + .getRequest() + .setMethod(Bundle.HTTPVerb.PUT) + .setUrl("Patient?identifier=http://example.com|test"); + + // create MessageHeader + MessageHeader messageHeader = new MessageHeader(); + messageHeader.getMeta().setExtension(messageHeaderAutoVersionExtension); + // add reference + messageHeader.addFocus().setReference("urn:uuid:00001"); + + bundle.addEntry() + .setResource(messageHeader) + .getRequest() + .setMethod(Bundle.HTTPVerb.POST) + .setUrl("/MessageHeader"); + + return bundle; + }; + + // create bundle first time + Bundle outcome = createAndValidateBundle(supplier.get(), + List.of("201 Created", "201 Created"), List.of("1", "1")); + IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + IdType messageHeaderId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + // read back and verify that reference is versioned and correct + Patient patient = myPatientDao.read(patientId); + MessageHeader messageHeader = myMessageHeaderDao.read(messageHeaderId); + assertEquals(patient.getIdElement().getValue(), messageHeader.getFocus().get(0).getReference()); + + // create bundle second time + outcome = createAndValidateBundle(supplier.get(), List.of("200 OK", "201 Created"), List.of("1", "1")); + patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation()); + messageHeaderId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()); + + // read back and verify that reference is versioned and correct + patient = myPatientDao.read(patientId); + messageHeader = myMessageHeaderDao.read(messageHeaderId); + assertEquals(patient.getIdElement().getValue(), messageHeader.getFocus().get(0).getReference()); + } + + private Bundle createAndValidateBundle(Bundle theBundle, List theOutcomeStatuses, + List theOutcomeVersions) { + assertEquals(theBundle.getEntry().size(), theOutcomeStatuses.size(), + "Size of OutcomeStatuses list is incorrect"); + assertEquals(theBundle.getEntry().size(), theOutcomeVersions.size(), + "Size of OutcomeVersions list is incorrect"); + + ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle)); + Bundle result = mySystemDao.transaction(mySrd, theBundle); + ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(theBundle)); + + for (int i = 0; i < result.getEntry().size(); i++) { + assertEquals(theOutcomeStatuses.get(i), result.getEntry().get(i).getResponse().getStatus()); + IIdType resultId = new IdType(result.getEntry().get(i).getResponse().getLocation()); + assertEquals(theOutcomeVersions.get(i), resultId.getVersionIdPart()); + } + return result; + } + private Patient createAndUpdatePatient(String thePatientId) { Patient patient = new Patient(); patient.setId(thePatientId); diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java index 08cb4b8b03fa..05f912d0150b 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -1806,10 +1806,7 @@ private void resolveReferencesThenSaveAndIndexResource( theDaoMethodOutcome.setId(newId); - IIdType target = theIdSubstitutions.getForSource(newId); - if (target != null) { - target.setValue(newId.getValue()); - } + theIdSubstitutions.updateTargets(newId); if (theDaoMethodOutcome.getOperationOutcome() != null) { IBase responseEntry = entriesToProcess.getResponseBundleEntryWithVersionlessComparison(newId); diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMap.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMap.java index 26130af1b2dc..66a17baedefd 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMap.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMap.java @@ -27,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; public class IdSubstitutionMap { @@ -87,6 +88,22 @@ public boolean isEmpty() { return myMap.isEmpty(); } + /** + * Updates all targets of the map with a new id value if the input id has + * the same ResourceType and IdPart as the target id. + */ + public void updateTargets(IIdType theNewId) { + if (theNewId == null) { + return; + } + String newUnqualifiedVersionLessId = theNewId.toUnqualifiedVersionless().getValue(); + entrySet().stream() + .map(Pair::getValue) + .filter(targetId -> + Objects.equals(targetId.toUnqualifiedVersionless().getValue(), newUnqualifiedVersionLessId)) + .forEach(targetId -> targetId.setValue(theNewId.getValue())); + } + private static class Entry { private final String myUnversionedId; diff --git a/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMapTest.java b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMapTest.java new file mode 100644 index 000000000000..1bfb76dd3759 --- /dev/null +++ b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/dao/IdSubstitutionMapTest.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jpa.dao; + +import org.hl7.fhir.r4.model.IdType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@ExtendWith(MockitoExtension.class) +public class IdSubstitutionMapTest { + + private IdSubstitutionMap idSubstitutions; + + @BeforeEach + void setUp() { + idSubstitutions = new IdSubstitutionMap(); + } + + @ParameterizedTest + @CsvSource({ + "Patient/123/_history/3, Patient/123/_history/2", + "Patient/123/_history/3, Patient/123" + }) + void testUpdateTargets_inputMatchesTarget_onlyMatchedTargetUpdated(String theInputId, String theTargetId) { + idSubstitutions.put(new IdType("urn:uuid:1234"), new IdType(theTargetId)); + idSubstitutions.put(new IdType("urn:uuid:5000"), new IdType("Patient/5000")); + idSubstitutions.put(new IdType("urn:uuid:6000"), new IdType("Patient/6000_history/3")); + + idSubstitutions.updateTargets(new IdType(theInputId)); + + assertEquals(theInputId, idSubstitutions.getForSource("urn:uuid:1234").getValue()); + assertEquals("Patient/5000", idSubstitutions.getForSource("urn:uuid:5000").getValue()); + assertEquals("Patient/6000_history/3", idSubstitutions.getForSource("urn:uuid:6000").getValue()); + } + + @Test + void testUpdateTargets_inputMatchesAllTargets_allTargetsUpdated() { + idSubstitutions.put(new IdType("urn:uuid:1234"), new IdType("Patient/123/_history/1")); + idSubstitutions.put(new IdType("urn:uuid:5000"), new IdType("Patient/123/_history/2")); + idSubstitutions.put(new IdType("urn:uuid:6000"), new IdType("Patient/123/_history/4")); + + idSubstitutions.updateTargets(new IdType("Patient/123/_history/3")); + + assertEquals("Patient/123/_history/3", idSubstitutions.getForSource("urn:uuid:1234").getValue()); + assertEquals("Patient/123/_history/3", idSubstitutions.getForSource("urn:uuid:5000").getValue()); + assertEquals("Patient/123/_history/3", idSubstitutions.getForSource("urn:uuid:6000").getValue()); + } + + @ParameterizedTest + @ValueSource(strings = {"Patient/124", "Patient/124/_history/3", "Patient", ""}) + void testUpdateTargets_noMatchingTarget_noUpdate(String theInputId) { + idSubstitutions.put(new IdType("urn:uuid:1234"), new IdType("Patient/123/_history/3")); + idSubstitutions.updateTargets(new IdType(theInputId)); + assertEquals("Patient/123/_history/3", idSubstitutions.getForSource("urn:uuid:1234").getValue()); + } + + @Test + void testUpdateTargets_nullInputId_noExceptionAndNoUpdate() { + idSubstitutions.put(new IdType("urn:uuid:1234"), new IdType("Patient/123/_history/3")); + idSubstitutions.updateTargets(null); + assertEquals("Patient/123/_history/3", idSubstitutions.getForSource("urn:uuid:1234").getValue()); + } +} From d3876c546fdb0b8f4029f5fccc085aaef2cbe9c2 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Fri, 26 Jan 2024 11:09:01 -0500 Subject: [PATCH 08/24] Oracle: Ensure migrated database still takes large resource text updates (#5629) * First pass at fix to Oracle HFJ_RES_VER.RES_TEXT_VC migration. * First stab at agreed upon solution. * Fix error with 4001 by removing unnecessary annotation. * Spotless and TODO. * Remove annotation for good and set length to LONG32. * Fix copyright year. * Finalize changelog. * Remove migration changes. Fix unit test. * Fix compile error. * Log output. * Refactor resource history code into new ResourceHistoryCalculator. * Spotless. * Convert record to POJO. * Restore pre-17 switch statement. * Finalize new resource history calculator code and tests. * Spotless. * Remove logging. * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5633-oracle-hfj-res-ver-clob-migration.yaml Apply code reviewer suggestion Co-authored-by: Michael Buckley * Code review feedback. --------- Co-authored-by: Michael Buckley --- ...633-oracle-hfj-res-ver-clob-migration.yaml | 5 + .../config/HibernatePropertiesProvider.java | 4 + .../ca/uhn/fhir/jpa/config/JpaConfig.java | 7 + .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 63 ++-- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 12 +- .../ca/uhn/fhir/jpa/dao/EncodedResource.java | 9 + .../jpa/dao/ResourceHistoryCalculator.java | 134 +++++++ .../fhir/jpa/dao/ResourceHistoryState.java | 86 +++++ .../tasks/HapiFhirJpaMigrationTasks.java | 3 + .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 12 +- .../dao/ResourceHistoryCalculatorTest.java | 326 ++++++++++++++++++ .../model/entity/ResourceHistoryTable.java | 14 +- .../stresstest/GiantTransactionPerfTest.java | 4 + 13 files changed, 634 insertions(+), 45 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5633-oracle-hfj-res-ver-clob-migration.yaml create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculatorTest.java diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5633-oracle-hfj-res-ver-clob-migration.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5633-oracle-hfj-res-ver-clob-migration.yaml new file mode 100644 index 000000000000..08edbe2b2b7f --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5633-oracle-hfj-res-ver-clob-migration.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5633 +title: "Smile failed to save resources running on Oracle when installed from 2023-02 or earlier. + This has been fixed." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HibernatePropertiesProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HibernatePropertiesProvider.java index 74702646d6bd..38d3e6747d8f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HibernatePropertiesProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/HibernatePropertiesProvider.java @@ -75,4 +75,8 @@ public String getHibernateSearchBackend() { public DataSource getDataSource() { return myEntityManagerFactory.getDataSource(); } + + public boolean isOracleDialect() { + return getDialect() instanceof org.hibernate.dialect.OracleDialect; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java index 422f5a86f019..e60974ae642d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/JpaConfig.java @@ -51,6 +51,7 @@ import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.JpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.MatchResourceUrlService; +import ca.uhn.fhir.jpa.dao.ResourceHistoryCalculator; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.data.IResourceModifiedDao; @@ -869,4 +870,10 @@ public IResourceModifiedMessagePersistenceSvc subscriptionMessagePersistence( public IMetaTagSorter metaTagSorter() { return new MetaTagSorterAlphabetical(); } + + @Bean + public ResourceHistoryCalculator resourceHistoryCalculator( + FhirContext theFhirContext, HibernatePropertiesProvider theHibernatePropertiesProvider) { + return new ResourceHistoryCalculator(theFhirContext, theHibernatePropertiesProvider.isOracleDialect()); + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index c8c6a3fc9fb3..06200fc852a5 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -85,7 +85,6 @@ import ca.uhn.fhir.model.base.composite.BaseCodingDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -105,8 +104,6 @@ import com.google.common.base.Charsets; import com.google.common.collect.Sets; import com.google.common.hash.HashCode; -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import jakarta.annotation.PostConstruct; @@ -264,6 +261,9 @@ public abstract class BaseHapiFhirDao extends BaseStora @Autowired private PlatformTransactionManager myTransactionManager; + @Autowired + protected ResourceHistoryCalculator myResourceHistoryCalculator; + protected final CodingSpy myCodingSpy = new CodingSpy(); @VisibleForTesting @@ -277,6 +277,11 @@ public void setSearchParamPresenceSvc(ISearchParamPresenceSvc theSearchParamPres mySearchParamPresenceSvc = theSearchParamPresenceSvc; } + @VisibleForTesting + public void setResourceHistoryCalculator(ResourceHistoryCalculator theResourceHistoryCalculator) { + myResourceHistoryCalculator = theResourceHistoryCalculator; + } + @Override protected IInterceptorBroadcaster getInterceptorBroadcaster() { return myInterceptorBroadcaster; @@ -643,6 +648,7 @@ protected EncodedResource populateResourceIntoEntity( theEntity.setResourceType(toResourceName(theResource)); } + byte[] resourceBinary; String resourceText; ResourceEncodingEnum encoding; boolean changed = false; @@ -659,6 +665,7 @@ protected EncodedResource populateResourceIntoEntity( if (address != null) { encoding = ResourceEncodingEnum.ESR; + resourceBinary = null; resourceText = address.getProviderId() + ":" + address.getLocation(); changed = true; @@ -675,10 +682,15 @@ protected EncodedResource populateResourceIntoEntity( theEntity.setFhirVersion(myContext.getVersion().getVersion()); - HashFunction sha256 = Hashing.sha256(); - resourceText = encodeResource(theResource, encoding, excludeElements, myContext); - encoding = ResourceEncodingEnum.JSON; - HashCode hashCode = sha256.hashUnencodedChars(resourceText); + // TODO: LD: Once 2024-02 it out the door we should consider further refactoring here to move + // more of this logic within the calculator and eliminate more local variables + final ResourceHistoryState calculate = myResourceHistoryCalculator.calculateResourceHistoryState( + theResource, encoding, excludeElements); + + resourceText = calculate.getResourceText(); + resourceBinary = calculate.getResourceBinary(); + encoding = calculate.getEncoding(); // This may be a no-op + final HashCode hashCode = calculate.getHashCode(); String hashSha256 = hashCode.toString(); if (!hashSha256.equals(theEntity.getHashSha256())) { @@ -696,6 +708,7 @@ protected EncodedResource populateResourceIntoEntity( } else { encoding = null; + resourceBinary = null; resourceText = null; } @@ -713,6 +726,7 @@ protected EncodedResource populateResourceIntoEntity( changed = true; } + resourceBinary = null; resourceText = null; encoding = ResourceEncodingEnum.DEL; } @@ -737,13 +751,17 @@ protected EncodedResource populateResourceIntoEntity( if (currentHistoryVersion == null || !currentHistoryVersion.hasResource()) { changed = true; } else { - changed = !StringUtils.equals(currentHistoryVersion.getResourceTextVc(), resourceText); + // TODO: LD: Once 2024-02 it out the door we should consider further refactoring here to move + // more of this logic within the calculator and eliminate more local variables + changed = myResourceHistoryCalculator.isResourceHistoryChanged( + currentHistoryVersion, resourceBinary, resourceText); } } } EncodedResource retVal = new EncodedResource(); retVal.setEncoding(encoding); + retVal.setResourceBinary(resourceBinary); retVal.setResourceText(resourceText); retVal.setChanged(changed); @@ -1393,8 +1411,11 @@ public IBasePersistedResource updateHistoryEntity( ResourceEncodingEnum encoding = myStorageSettings.getResourceEncoding(); List excludeElements = new ArrayList<>(8); getExcludedElements(historyEntity.getResourceType(), excludeElements, theResource.getMeta()); - String encodedResourceString = encodeResource(theResource, encoding, excludeElements, myContext); - boolean changed = !StringUtils.equals(historyEntity.getResourceTextVc(), encodedResourceString); + String encodedResourceString = + myResourceHistoryCalculator.encodeResource(theResource, encoding, excludeElements); + byte[] resourceBinary = ResourceHistoryCalculator.getResourceBinary(encoding, encodedResourceString); + final boolean changed = myResourceHistoryCalculator.isResourceHistoryChanged( + historyEntity, resourceBinary, encodedResourceString); historyEntity.setUpdated(theTransactionDetails.getTransactionDate()); @@ -1406,14 +1427,15 @@ public IBasePersistedResource updateHistoryEntity( return historyEntity; } - populateEncodedResource(encodedResource, encodedResourceString, ResourceEncodingEnum.JSON); + myResourceHistoryCalculator.populateEncodedResource( + encodedResource, encodedResourceString, resourceBinary, encoding); } - /* * Save the resource itself to the resourceHistoryTable */ historyEntity = myEntityManager.merge(historyEntity); historyEntity.setEncoding(encodedResource.getEncoding()); + historyEntity.setResource(encodedResource.getResourceBinary()); historyEntity.setResourceTextVc(encodedResource.getResourceText()); myResourceHistoryTableDao.save(historyEntity); @@ -1423,8 +1445,12 @@ public IBasePersistedResource updateHistoryEntity( } private void populateEncodedResource( - EncodedResource encodedResource, String encodedResourceString, ResourceEncodingEnum theEncoding) { + EncodedResource encodedResource, + String encodedResourceString, + byte[] theResourceBinary, + ResourceEncodingEnum theEncoding) { encodedResource.setResourceText(encodedResourceString); + encodedResource.setResourceBinary(theResourceBinary); encodedResource.setEncoding(theEncoding); } @@ -1489,6 +1515,7 @@ private void createHistoryEntry( } historyEntry.setEncoding(theChanged.getEncoding()); + historyEntry.setResource(theChanged.getResourceBinary()); historyEntry.setResourceTextVc(theChanged.getResourceText()); ourLog.debug("Saving history entry ID[{}] for RES_ID[{}]", historyEntry.getId(), historyEntry.getResourceId()); @@ -1926,16 +1953,6 @@ public static String decodeResource(byte[] theResourceBytes, ResourceEncodingEnu return resourceText; } - public static String encodeResource( - IBaseResource theResource, - ResourceEncodingEnum theEncoding, - List theExcludeElements, - FhirContext theContext) { - IParser parser = theEncoding.newParser(theContext); - parser.setDontEncodeElements(theExcludeElements); - return parser.encodeResourceToString(theResource); - } - private static String parseNarrativeTextIntoWords(IBaseResource theResource) { StringBuilder b = new StringBuilder(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 079f9dc82158..3d7fca9b04af 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -1709,17 +1709,11 @@ private void reindexOptimizeStorageHistoryEntity(ResourceTable entity, ResourceH if (historyEntity.getEncoding() == ResourceEncodingEnum.JSONC || historyEntity.getEncoding() == ResourceEncodingEnum.JSON) { byte[] resourceBytes = historyEntity.getResource(); - - // Always migrate data out of the bytes column if (resourceBytes != null) { String resourceText = decodeResource(resourceBytes, historyEntity.getEncoding()); - ourLog.debug( - "Storing text of resource {} version {} as inline VARCHAR", - entity.getResourceId(), - historyEntity.getVersion()); - historyEntity.setResourceTextVc(resourceText); - historyEntity.setEncoding(ResourceEncodingEnum.JSON); - changed = true; + if (myResourceHistoryCalculator.conditionallyAlterHistoryEntity(entity, historyEntity, resourceText)) { + changed = true; + } } } if (isBlank(historyEntity.getSourceUri()) && isBlank(historyEntity.getRequestId())) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EncodedResource.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EncodedResource.java index d1d85f777275..cccce26e2263 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EncodedResource.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/EncodedResource.java @@ -24,6 +24,7 @@ class EncodedResource { private boolean myChanged; + private byte[] myResource; private ResourceEncodingEnum myEncoding; private String myResourceText; @@ -35,6 +36,14 @@ public void setEncoding(ResourceEncodingEnum theEncoding) { myEncoding = theEncoding; } + public byte[] getResourceBinary() { + return myResource; + } + + public void setResourceBinary(byte[] theResource) { + myResource = theResource; + } + public boolean isChanged() { return myChanged; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java new file mode 100644 index 000000000000..fafa3c4ca293 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.parser.IParser; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +/** + * Responsible for various resource history-centric and {@link FhirContext} aware operations called by + * {@link BaseHapiFhirDao} or {@link BaseHapiFhirResourceDao} that require knowledge of whether an Oracle database is + * being used. + */ +public class ResourceHistoryCalculator { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceHistoryCalculator.class); + private static final HashFunction SHA_256 = Hashing.sha256(); + + private final FhirContext myFhirContext; + private final boolean myIsOracleDialect; + + public ResourceHistoryCalculator(FhirContext theFhirContext, boolean theIsOracleDialect) { + myFhirContext = theFhirContext; + myIsOracleDialect = theIsOracleDialect; + } + + ResourceHistoryState calculateResourceHistoryState( + IBaseResource theResource, ResourceEncodingEnum theEncoding, List theExcludeElements) { + final String encodedResource = encodeResource(theResource, theEncoding, theExcludeElements); + final byte[] resourceBinary; + final String resourceText; + final ResourceEncodingEnum encoding; + final HashCode hashCode; + + if (myIsOracleDialect) { + resourceText = null; + resourceBinary = getResourceBinary(theEncoding, encodedResource); + encoding = theEncoding; + hashCode = SHA_256.hashBytes(resourceBinary); + } else { + resourceText = encodedResource; + resourceBinary = null; + encoding = ResourceEncodingEnum.JSON; + hashCode = SHA_256.hashUnencodedChars(encodedResource); + } + + return new ResourceHistoryState(resourceText, resourceBinary, encoding, hashCode); + } + + boolean conditionallyAlterHistoryEntity( + ResourceTable theEntity, ResourceHistoryTable theHistoryEntity, String theResourceText) { + if (!myIsOracleDialect) { + ourLog.debug( + "Storing text of resource {} version {} as inline VARCHAR", + theEntity.getResourceId(), + theHistoryEntity.getVersion()); + theHistoryEntity.setResourceTextVc(theResourceText); + theHistoryEntity.setResource(null); + theHistoryEntity.setEncoding(ResourceEncodingEnum.JSON); + return true; + } + + return false; + } + + boolean isResourceHistoryChanged( + ResourceHistoryTable theCurrentHistoryVersion, + @Nullable byte[] theResourceBinary, + @Nullable String resourceText) { + if (myIsOracleDialect) { + return !Arrays.equals(theCurrentHistoryVersion.getResource(), theResourceBinary); + } + + return !StringUtils.equals(theCurrentHistoryVersion.getResourceTextVc(), resourceText); + } + + String encodeResource( + IBaseResource theResource, ResourceEncodingEnum theEncoding, List theExcludeElements) { + final IParser parser = theEncoding.newParser(myFhirContext); + parser.setDontEncodeElements(theExcludeElements); + return parser.encodeResourceToString(theResource); + } + + /** + * helper for returning the encoded byte array of the input resource string based on the theEncoding. + * + * @param theEncoding the theEncoding to used + * @param theEncodedResource the resource to encode + * @return byte array of the resource + */ + @Nonnull + static byte[] getResourceBinary(ResourceEncodingEnum theEncoding, String theEncodedResource) { + switch (theEncoding) { + case JSON: + return theEncodedResource.getBytes(StandardCharsets.UTF_8); + case JSONC: + return GZipUtil.compress(theEncodedResource); + default: + return new byte[0]; + } + } + + void populateEncodedResource( + EncodedResource theEncodedResource, + String theEncodedResourceString, + @Nullable byte[] theResourceBinary, + ResourceEncodingEnum theEncoding) { + if (myIsOracleDialect) { + populateEncodedResourceInner(theEncodedResource, null, theResourceBinary, theEncoding); + } else { + populateEncodedResourceInner(theEncodedResource, theEncodedResourceString, null, ResourceEncodingEnum.JSON); + } + } + + private void populateEncodedResourceInner( + EncodedResource encodedResource, + String encodedResourceString, + byte[] theResourceBinary, + ResourceEncodingEnum theEncoding) { + encodedResource.setResourceText(encodedResourceString); + encodedResource.setResourceBinary(theResourceBinary); + encodedResource.setEncoding(theEncoding); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java new file mode 100644 index 000000000000..6da5f9ab5941 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java @@ -0,0 +1,86 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; +import com.google.common.hash.HashCode; +import jakarta.annotation.Nullable; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * POJO to contain the results of {@link ResourceHistoryCalculator#calculateResourceHistoryState(IBaseResource, ResourceEncodingEnum, List)} + */ +public class ResourceHistoryState { + @Nullable + private final String myResourceText; + + @Nullable + private final byte[] myResourceBinary; + + private final ResourceEncodingEnum myEncoding; + private final HashCode myHashCode; + + public ResourceHistoryState( + @Nullable String theResourceText, + @Nullable byte[] theResourceBinary, + ResourceEncodingEnum theEncoding, + HashCode theHashCode) { + myResourceText = theResourceText; + myResourceBinary = theResourceBinary; + myEncoding = theEncoding; + myHashCode = theHashCode; + } + + @Nullable + public String getResourceText() { + return myResourceText; + } + + @Nullable + public byte[] getResourceBinary() { + return myResourceBinary; + } + + public ResourceEncodingEnum getEncoding() { + return myEncoding; + } + + public HashCode getHashCode() { + return myHashCode; + } + + @Override + public boolean equals(Object theO) { + if (this == theO) { + return true; + } + if (theO == null || getClass() != theO.getClass()) { + return false; + } + ResourceHistoryState that = (ResourceHistoryState) theO; + return Objects.equals(myResourceText, that.myResourceText) + && Arrays.equals(myResourceBinary, that.myResourceBinary) + && myEncoding == that.myEncoding + && Objects.equals(myHashCode, that.myHashCode); + } + + @Override + public int hashCode() { + int result = Objects.hash(myResourceText, myEncoding, myHashCode); + result = 31 * result + Arrays.hashCode(myResourceBinary); + return result; + } + + @Override + public String toString() { + return new StringJoiner(", ", ResourceHistoryState.class.getSimpleName() + "[", "]") + .add("myResourceText='" + myResourceText + "'") + .add("myResourceBinary=" + Arrays.toString(myResourceBinary)) + .add("myEncoding=" + myEncoding) + .add("myHashCode=" + myHashCode) + .toString(); + } +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 636b436746ea..340a6cbee2dc 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -627,6 +627,9 @@ protected void init660() { version.executeRawSqls("20230402.1", Map.of(DriverTypeEnum.POSTGRES_9_4, postgresTuningStatements)); // Use an unlimited length text column for RES_TEXT_VC + // N.B. This will FAIL SILENTLY on Oracle due to the fact that Oracle does not support an ALTER TABLE from + // VARCHAR to + // CLOB. Because of failureAllowed() this won't halt the migration version.onTable("HFJ_RES_VER") .modifyColumn("20230421.1", "RES_TEXT_VC") .nullable() diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index af712a2e197b..4c3420ea4eaa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -649,7 +649,13 @@ private void expandValueSetIntoAccumulator( .getMessage(TermReadSvcImpl.class, "valueSetExpandedUsingPreExpansion", expansionTimestamp); theAccumulator.addMessage(msg); expandConcepts( - theExpansionOptions, theAccumulator, termValueSet, theFilter, theAdd, theAddedCodes, isOracleDialect()); + theExpansionOptions, + theAccumulator, + termValueSet, + theFilter, + theAdd, + theAddedCodes, + myHibernatePropertiesProvider.isOracleDialect()); } @Nonnull @@ -664,10 +670,6 @@ private String toHumanReadableExpansionTimestamp(TermValueSet termValueSet) { return expansionTimestamp; } - private boolean isOracleDialect() { - return myHibernatePropertiesProvider.getDialect() instanceof org.hibernate.dialect.OracleDialect; - } - private void expandConcepts( ValueSetExpansionOptions theExpansionOptions, IValueSetConceptAccumulator theAccumulator, diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculatorTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculatorTest.java new file mode 100644 index 000000000000..d2123cacbb56 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculatorTest.java @@ -0,0 +1,326 @@ +package ca.uhn.fhir.jpa.dao; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; +import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import com.google.common.hash.HashCode; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +import org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.hapi.ctx.FhirR4; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneId; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +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.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResourceHistoryCalculatorTest { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceHistoryCalculatorTest.class); + + private static final FhirContext CONTEXT = FhirContext.forR4Cached(); + + private static final ResourceHistoryCalculator CALCULATOR_ORACLE = new ResourceHistoryCalculator(CONTEXT, true); + private static final ResourceHistoryCalculator CALCULATOR_NON_ORACLE = new ResourceHistoryCalculator(CONTEXT, false); + + private static final LocalDate TODAY = LocalDate.of(2024, Month.JANUARY, 25); + private static final String ENCODED_RESOURCE_1 = "1234"; + private static final String ENCODED_RESOURCE_2 = "abcd"; + private static final String RESOURCE_TEXT_VC = "resourceTextVc"; + private static final List EXCLUDED_ELEMENTS_1 = List.of("id"); + private static final List EXCLUDED_ELEMENTS_2 = List.of("resourceType", "birthDate"); + private static final HashFunction SHA_256 = Hashing.sha256(); + + private static Stream calculateResourceHistoryStateArguments() { + return Stream.of( + Arguments.of(FhirContext.forDstu3Cached(), true, ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), false, ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), true, ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), false, ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), true, ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), false, ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), true, ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), false, ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), true, ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), false, ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), true, ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), false, ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), true, ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), false, ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), true, ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), false, ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_1) + ); + } + + /** + * The purpose of this test is to ensure that the conditional logic to pre-calculate resource history text or binaries + * is respected. + * If this is for Oracle, the resource text will be driven off a binary with a given encoding with the + * resource text effectively ignored. + * If this is not Oracle, it will be driven off a JSON encoded text field with + * the binary effectively ignored. + */ + @ParameterizedTest + @MethodSource("calculateResourceHistoryStateArguments") + void calculateResourceHistoryState(FhirContext theFhirContext, boolean theIsOracle, ResourceEncodingEnum theResourceEncoding, List theExcludedElements) { + final IBaseResource patient = getPatient(theFhirContext); + + final ResourceHistoryCalculator calculator = getCalculator(theFhirContext, theIsOracle); + final ResourceHistoryState result = calculator.calculateResourceHistoryState(patient, theResourceEncoding, theExcludedElements); + + if (theIsOracle) { + assertNotNull(result.getResourceBinary()); // On Oracle: We use the resource binary to serve up the resource content + assertNull(result.getResourceText()); // On Oracle: We do NOT use the resource text to serve up the resource content + assertEquals(theResourceEncoding, result.getEncoding()); // On Oracle, the resource encoding is what we used to encode the binary + assertEquals(SHA_256.hashBytes(result.getResourceBinary()), result.getHashCode()); // On Oracle, the SHA 256 hash is of the binary + } else { + assertNull(result.getResourceBinary()); // Non-Oracle: We do NOT use the resource binary to serve up the resource content + assertNotNull(result.getResourceText()); // Non-Oracle: We use the resource text to serve up the resource content + assertEquals(ResourceEncodingEnum.JSON, result.getEncoding()); // Non-Oracle, since we didn't encode a binary this is always JSON. + final HashCode expectedHashCode = SHA_256.hashUnencodedChars(calculator.encodeResource(patient, theResourceEncoding, theExcludedElements)); // Non-Oracle, the SHA 256 hash is of the parsed resource object + assertEquals(expectedHashCode, result.getHashCode()); + } + } + + + private static Stream conditionallyAlterHistoryEntityArguments() { + return Stream.of( + Arguments.of(true, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_2) + ); + } + + @ParameterizedTest + @MethodSource("conditionallyAlterHistoryEntityArguments") + void conditionallyAlterHistoryEntity_usesVarcharForOracle(boolean theIsOracle, ResourceEncodingEnum theResourceEncoding, String theResourceText) { + final ResourceTable resourceTable = new ResourceTable(); + resourceTable.setId(123L); + + final ResourceHistoryTable resourceHistoryTable = new ResourceHistoryTable(); + resourceHistoryTable.setVersion(1); + resourceHistoryTable.setResource("resource".getBytes(StandardCharsets.UTF_8)); + resourceHistoryTable.setEncoding(theResourceEncoding); + resourceHistoryTable.setResourceTextVc(RESOURCE_TEXT_VC); + + final boolean isChanged = + getCalculator(theIsOracle).conditionallyAlterHistoryEntity(resourceTable, resourceHistoryTable, theResourceText); + + if (theIsOracle) { + assertFalse(isChanged); + assertNotNull(resourceHistoryTable.getResource()); + assertEquals(RESOURCE_TEXT_VC, resourceHistoryTable.getResourceTextVc()); + assertEquals(resourceHistoryTable.getEncoding(), resourceHistoryTable.getEncoding()); + } else { + assertTrue(isChanged); + assertNull(resourceHistoryTable.getResource()); + assertEquals(theResourceText, resourceHistoryTable.getResourceTextVc()); + assertEquals(resourceHistoryTable.getEncoding(), ResourceEncodingEnum.JSON); + } + } + + private static Stream encodeResourceArguments() { + return Stream.of( + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forDstu3Cached(), ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.JSONC, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.DEL, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.ESR, EXCLUDED_ELEMENTS_2), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_1), + Arguments.of(FhirContext.forR4Cached(), ResourceEncodingEnum.JSON, EXCLUDED_ELEMENTS_2) + ); + } + + @ParameterizedTest + @MethodSource("encodeResourceArguments") + void encodeResource_ensureFhirVersionSpecificAndIntendedElementsExcluded(FhirContext theFhirContext, ResourceEncodingEnum theResourceEncoding, List theExcludedElements) { + final IBaseResource patient = getPatient(theFhirContext); + final String encodedResource = getCalculator(theFhirContext, true).encodeResource(patient, theResourceEncoding, theExcludedElements); + + final String expectedEncoding = + theResourceEncoding.newParser(theFhirContext).setDontEncodeElements(theExcludedElements).encodeResourceToString(patient); + + assertEquals(expectedEncoding, encodedResource); + } + + private static Stream getResourceBinaryArguments() { + return Stream.of( + Arguments.of(ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_1), + Arguments.of(ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_2), + Arguments.of(ResourceEncodingEnum.DEL, ENCODED_RESOURCE_1), + Arguments.of(ResourceEncodingEnum.DEL, ENCODED_RESOURCE_2), + Arguments.of(ResourceEncodingEnum.ESR, ENCODED_RESOURCE_1), + Arguments.of(ResourceEncodingEnum.ESR, ENCODED_RESOURCE_2), + Arguments.of(ResourceEncodingEnum.JSON, ENCODED_RESOURCE_1), + Arguments.of(ResourceEncodingEnum.JSON, ENCODED_RESOURCE_2) + ); + } + + @ParameterizedTest + @MethodSource("getResourceBinaryArguments") + void getResourceBinary(ResourceEncodingEnum theResourceEncoding, String theEncodedResource) { + final byte[] resourceBinary = ResourceHistoryCalculator.getResourceBinary(theResourceEncoding, theEncodedResource); + + switch (theResourceEncoding) { + case JSON: + assertArrayEquals(theEncodedResource.getBytes(StandardCharsets.UTF_8), resourceBinary); + break; + case JSONC: + assertArrayEquals(GZipUtil.compress(theEncodedResource), resourceBinary); + break; + case DEL : + case ESR : + default: + assertArrayEquals(new byte[0], resourceBinary); + } + + ourLog.info("resourceBinary: {}", resourceBinary); + } + + private static Stream isResourceHistoryChangedArguments() { + return Stream.of( + Arguments.of(true, ENCODED_RESOURCE_1.getBytes(StandardCharsets.UTF_8), ENCODED_RESOURCE_1), + Arguments.of(false, ENCODED_RESOURCE_1.getBytes(StandardCharsets.UTF_8), ENCODED_RESOURCE_1), + Arguments.of(true, ENCODED_RESOURCE_2.getBytes(StandardCharsets.UTF_8), ENCODED_RESOURCE_2), + Arguments.of(false, ENCODED_RESOURCE_2.getBytes(StandardCharsets.UTF_8), ENCODED_RESOURCE_2) + ); + } + + @ParameterizedTest + @MethodSource("isResourceHistoryChangedArguments") + void isResourceHistoryChanged(boolean theIsOracle, byte[] theNewBinary, String theNewResourceText) { + final String existngResourceText = ENCODED_RESOURCE_1; + final byte[] existingBytes = existngResourceText.getBytes(StandardCharsets.UTF_8); + + final ResourceHistoryTable resourceHistoryTable = new ResourceHistoryTable(); + resourceHistoryTable.setResource(existingBytes); + resourceHistoryTable.setResourceTextVc(existngResourceText); + + final boolean isChanged = getCalculator(theIsOracle).isResourceHistoryChanged(resourceHistoryTable, theNewBinary, theNewResourceText); + + if (theIsOracle) { + final boolean expectedResult = !Arrays.equals(existingBytes, theNewBinary); + assertEquals(expectedResult, isChanged); + } else { + final boolean expectedResult = ! existngResourceText.equals(theNewResourceText); + assertEquals(expectedResult, isChanged); + } + } + + private static Stream populateEncodedResourceArguments() { + return Stream.of( + Arguments.of(true, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.JSONC, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.DEL, ENCODED_RESOURCE_1), + Arguments.of(true, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_1), + Arguments.of(false, ResourceEncodingEnum.ESR, ENCODED_RESOURCE_2), + Arguments.of(true, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_2), + Arguments.of(false, ResourceEncodingEnum.JSON, ENCODED_RESOURCE_1) + ); + } + + @ParameterizedTest + @MethodSource("populateEncodedResourceArguments") + void populateEncodedResource(boolean theIsOracle, ResourceEncodingEnum theResourceEncoding, String theEncodedResourceString) { + final EncodedResource encodedResource = new EncodedResource(); + final byte[] resourceBinary = theEncodedResourceString.getBytes(StandardCharsets.UTF_8); + + getCalculator(theIsOracle) + .populateEncodedResource(encodedResource, theEncodedResourceString, resourceBinary, theResourceEncoding); + + if (theIsOracle) { + assertEquals(resourceBinary, encodedResource.getResourceBinary()); + assertNull(encodedResource.getResourceText()); + assertEquals(theResourceEncoding, encodedResource.getEncoding()); + } else { + assertNull(encodedResource.getResourceBinary()); + assertEquals(theEncodedResourceString, encodedResource.getResourceText()); + assertEquals(ResourceEncodingEnum.JSON, encodedResource.getEncoding()); + } + } + + private ResourceHistoryCalculator getCalculator(boolean theIsOracle) { + return theIsOracle ? CALCULATOR_ORACLE : CALCULATOR_NON_ORACLE; + } + + private ResourceHistoryCalculator getCalculator(FhirContext theFhirContext, boolean theIsOracle) { + return new ResourceHistoryCalculator(theFhirContext, theIsOracle); + } + + private IBaseResource getPatient(FhirContext theFhirContext) { + if (theFhirContext.getVersion() instanceof FhirR4) { + return getPatientR4(); + } + + if (theFhirContext.getVersion() instanceof FhirDstu3) { + return getPatientDstu3(); + } + + return null; + } + + private org.hl7.fhir.dstu3.model.Patient getPatientDstu3() { + final org.hl7.fhir.dstu3.model.Patient patient = new org.hl7.fhir.dstu3.model.Patient(); + + patient.setId("123"); + patient.setBirthDate(Date.from(TODAY.atStartOfDay(ZoneId.of("America/Toronto")).toInstant())); + + return patient; + } + + private Patient getPatientR4() { + final Patient patient = new Patient(); + + patient.setId("123"); + patient.setBirthDate(Date.from(TODAY.atStartOfDay(ZoneId.of("America/Toronto")).toInstant())); + + return patient; + } +} diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java index d9199787b726..49ad436112c9 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceHistoryTable.java @@ -32,8 +32,6 @@ import java.util.ArrayList; import java.util.Collection; -import static org.apache.commons.lang3.StringUtils.defaultString; - @Entity @Table( name = ResourceHistoryTable.HFJ_RES_VER, @@ -86,15 +84,12 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl @OneToMany(mappedBy = "myResourceHistory", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) private Collection myTags; - /** - * Note: No setter for this field because it's only a legacy way of storing data now. - */ @Column(name = "RES_TEXT", length = Integer.MAX_VALUE - 1, nullable = true) @Lob() @OptimisticLock(excluded = true) private byte[] myResource; - @Column(name = "RES_TEXT_VC", nullable = true, length = Length.LONG32) + @Column(name = "RES_TEXT_VC", length = Length.LONG32, nullable = true) @OptimisticLock(excluded = true) private String myResourceTextVc; @@ -155,8 +150,7 @@ public String getResourceTextVc() { } public void setResourceTextVc(String theResourceTextVc) { - myResource = null; - myResourceTextVc = defaultString(theResourceTextVc); + myResourceTextVc = theResourceTextVc; } public ResourceHistoryProvenanceEntity getProvenance() { @@ -212,6 +206,10 @@ public byte[] getResource() { return myResource; } + public void setResource(byte[] theResource) { + myResource = theResource; + } + @Override public Long getResourceId() { return myResourceId; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java index ec9d5d8a4d5c..dcce168fa099 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/stresstest/GiantTransactionPerfTest.java @@ -17,8 +17,10 @@ import ca.uhn.fhir.jpa.cache.ResourceChangeListenerRegistryImpl; import ca.uhn.fhir.jpa.cache.ResourcePersistentIdMap; import ca.uhn.fhir.jpa.cache.ResourceVersionMap; +import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.JpaResourceDao; +import ca.uhn.fhir.jpa.dao.ResourceHistoryCalculator; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer; @@ -148,6 +150,7 @@ public class GiantTransactionPerfTest { private IIdHelperService myIdHelperService; @Mock private IJpaStorageResourceParser myJpaStorageResourceParser; + private final ResourceHistoryCalculator myResourceHistoryCalculator = new ResourceHistoryCalculator(FhirContext.forR4Cached(), false); private IMetaTagSorter myMetaTagSorter; @AfterEach @@ -271,6 +274,7 @@ public void beforeEach() { myEobDao.setJpaStorageResourceParserForUnitTest(myJpaStorageResourceParser); myEobDao.setExternallyStoredResourceServiceRegistryForUnitTest(new ExternallyStoredResourceServiceRegistry()); myEobDao.setMyMetaTagSorter(myMetaTagSorter); + myEobDao.setResourceHistoryCalculator(myResourceHistoryCalculator); myEobDao.start(); myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao)); From c7fd01519508edcaed03fb730b04c697b9fd10c2 Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Mon, 29 Jan 2024 13:18:40 -0800 Subject: [PATCH 09/24] Fix expansion for `ValueSet` with no concepts based on CodeSystem `urn:ietf:bcp:13` (#5638) * When fetching the mimetype code system, return empty CodeSystem with NOTSUPPORTED content. Update expansion logic to handle this case. Add some test cases. * Minor change to fix test * Rename changelog file * Remove TODOs as they're replaced by reported issue * Revert accidental change added with TODO removal --- ...eset-no-concepts-with-mimetype-system.yaml | 5 + .../CommonCodeSystemsTerminologyService.java | 11 +- ...oryTerminologyServerValidationSupport.java | 53 +++--- ...mmonCodeSystemsTerminologyServiceTest.java | 132 ++++++++++++++- ...erminologyServerValidationSupportTest.java | 156 +++++++++++++++++- 5 files changed, 320 insertions(+), 37 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5634-failure-expanding-valueset-no-concepts-with-mimetype-system.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5634-failure-expanding-valueset-no-concepts-with-mimetype-system.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5634-failure-expanding-valueset-no-concepts-with-mimetype-system.yaml new file mode 100644 index 000000000000..89f6c87c2b46 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5634-failure-expanding-valueset-no-concepts-with-mimetype-system.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5634 +title: "Previously, expanding a 'ValueSet' with no concepts based on system `urn:ietf:bcp:13` would fail with +`ExpansionCouldNotBeCompletedInternallyException`. This has been fixed." diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java index d463003bf9fc..a0fd67b0f81a 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java @@ -29,6 +29,7 @@ import org.hl7.fhir.dstu2.model.ValueSet; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r5.model.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -428,21 +429,27 @@ private LookupCodeResult lookupUcumCode(String theCode) { @Override public IBaseResource fetchCodeSystem(String theSystem) { - + final CodeSystemContentMode content; Map map; switch (defaultString(theSystem)) { case COUNTRIES_CODESYSTEM_URL: map = ISO_3166_CODES; + content = CodeSystemContentMode.COMPLETE; break; case CURRENCIES_CODESYSTEM_URL: map = ISO_4217_CODES; + content = CodeSystemContentMode.COMPLETE; + break; + case MIMETYPES_CODESYSTEM_URL: + map = Collections.emptyMap(); + content = CodeSystemContentMode.NOTPRESENT; break; default: return null; } CodeSystem retVal = new CodeSystem(); - retVal.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + retVal.setContent(content); retVal.setUrl(theSystem); for (Map.Entry nextEntry : map.entrySet()) { retVal.addConcept().setCode(nextEntry.getKey()).setDisplay(nextEntry.getValue()); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java index 84c4657a51e3..3c4cca0a8653 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java @@ -745,13 +745,6 @@ private org.hl7.fhir.r5.model.ValueSet expandValueSetR4B( return expandValueSetR5(theValidationSupportContext, input, theWantSystemUrlAndVersion, theWantCode); } - @Nullable - private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( - ValidationSupportContext theValidationSupportContext, org.hl7.fhir.r5.model.ValueSet theInput) - throws ExpansionCouldNotBeCompletedInternallyException { - return expandValueSetR5(theValidationSupportContext, theInput, null, null); - } - @Nullable private org.hl7.fhir.r5.model.ValueSet expandValueSetR5( ValidationSupportContext theValidationSupportContext, @@ -909,20 +902,25 @@ private boolean expandValueSetR5IncludeOrExclude( includeOrExcludeSystemResource = codeSystemLoader.apply(loadedCodeSystemUrl); - Set wantCodes; - if (theInclude.getConcept().isEmpty()) { - wantCodes = null; + boolean isIncludeWithDeclaredConcepts = !theInclude.getConcept().isEmpty(); + + final Set wantCodes; + if (isIncludeWithDeclaredConcepts) { + wantCodes = theInclude.getConcept().stream() + .map(org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent::getCode) + .collect(Collectors.toSet()); } else { - wantCodes = - theInclude.getConcept().stream().map(t -> t.getCode()).collect(Collectors.toSet()); + wantCodes = null; } boolean ableToHandleCode = false; String failureMessage = null; FailureType failureType = FailureType.OTHER; - if (includeOrExcludeSystemResource == null - || includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT) { + boolean isIncludeCodeSystemIgnored = includeOrExcludeSystemResource != null + && includeOrExcludeSystemResource.getContent() == Enumerations.CodeSystemContentMode.NOTPRESENT; + + if (includeOrExcludeSystemResource == null || isIncludeCodeSystemIgnored) { if (theWantCode != null) { if (theValidationSupportContext @@ -971,7 +969,7 @@ private boolean expandValueSetR5IncludeOrExclude( // If the ValueSet.compose.include has no individual concepts in it, and // we can't find the actual referenced CodeSystem, we have no choice // but to fail - if (!theInclude.getConcept().isEmpty()) { + if (isIncludeWithDeclaredConcepts) { ableToHandleCode = true; } else { failureMessage = getFailureMessageForMissingOrUnusableCodeSystem( @@ -998,15 +996,22 @@ private boolean expandValueSetR5IncludeOrExclude( } } } else { - if (isNotBlank(theInclude.getSystem()) - && !theInclude.getConcept().isEmpty() - && theInclude.getFilter().isEmpty() - && theInclude.getValueSet().isEmpty()) { - theInclude.getConcept().stream() - .map(t -> new FhirVersionIndependentConcept( - theInclude.getSystem(), t.getCode(), t.getDisplay(), theInclude.getVersion())) - .forEach(t -> nextCodeList.add(t)); - ableToHandleCode = true; + boolean isIncludeFromSystem = isNotBlank(theInclude.getSystem()) + && theInclude.getValueSet().isEmpty(); + boolean isIncludeWithFilter = !theInclude.getFilter().isEmpty(); + if (isIncludeFromSystem && !isIncludeWithFilter) { + if (isIncludeWithDeclaredConcepts) { + theInclude.getConcept().stream() + .map(t -> new FhirVersionIndependentConcept( + theInclude.getSystem(), + t.getCode(), + t.getDisplay(), + theInclude.getVersion())) + .forEach(nextCodeList::add); + ableToHandleCode = true; + } else if (isIncludeCodeSystemIgnored) { + ableToHandleCode = true; + } } if (!ableToHandleCode) { diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java index 21a2799eb162..8e7c63a23099 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java @@ -3,16 +3,24 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.support.ConceptValidationOptions; import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.context.support.LookupCodeRequest; +import ca.uhn.fhir.context.support.ValidationSupportContext; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode; import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r5.model.Enumerations; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL; +import static org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService.MIMETYPES_VALUESET_URL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -134,6 +142,7 @@ public void testLanguages_CommonLanguagesVs_GoodCode() { @Test public void testLanguages_CommonLanguagesVs_OnlyLanguage_NoRegion() { IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "nl")); + assertNotNull(nl); assertTrue(nl.isFound()); assertEquals("Dutch", nl.getCodeDisplay()); } @@ -141,6 +150,7 @@ public void testLanguages_CommonLanguagesVs_OnlyLanguage_NoRegion() { @Test public void testLanguages_CommonLanguagesVs_LanguageAndRegion() { IValidationSupport.LookupCodeResult nl = mySvc.lookupCode(newSupport(), new LookupCodeRequest("urn:ietf:bcp:47", "nl-NL")); + assertNotNull(nl); assertTrue(nl.isFound()); assertEquals("Dutch Netherlands", nl.getCodeDisplay()); } @@ -190,6 +200,7 @@ public void testFetchCodeSystemBuiltIn_Iso3166_R4() { CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem(CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL); assert cs != null; assertEquals(498, cs.getConcept().size()); + assertEquals(CodeSystemContentMode.COMPLETE, cs.getContent()); } @Test @@ -198,6 +209,7 @@ public void testFetchCodeSystemBuiltIn_Iso3166_DSTU3() { org.hl7.fhir.dstu3.model.CodeSystem cs = (org.hl7.fhir.dstu3.model.CodeSystem) svc.fetchCodeSystem(CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL); assert cs != null; assertEquals(498, cs.getConcept().size()); + assertEquals(org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode.COMPLETE, cs.getContent()); } @Test @@ -206,6 +218,7 @@ public void testFetchCodeSystemBuiltIn_Iso3166_R5() { org.hl7.fhir.r5.model.CodeSystem cs = (org.hl7.fhir.r5.model.CodeSystem) svc.fetchCodeSystem(CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL); assert cs != null; assertEquals(498, cs.getConcept().size()); + assertEquals(Enumerations.CodeSystemContentMode.COMPLETE, cs.getContent()); } @Test @@ -216,10 +229,11 @@ public void testFetchCodeSystemBuiltIn_Iso3166_DSTU2() { } @Test - public void testFetchCodeSystemBuiltIn_Iso_R4() { + public void testFetchCodeSystemBuiltIn_Iso4217_R4() { CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem(CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL); assert cs != null; assertEquals(182, cs.getConcept().size()); + assertEquals(CodeSystemContentMode.COMPLETE, cs.getContent()); } @Test @@ -239,6 +253,120 @@ public void testFetchCodeSystemUrlDstu3() { } } + @Test + public void testFetchCodeSystem_withMimeType_returnsOk() { + CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem(MIMETYPES_CODESYSTEM_URL); + assertNotNull(cs); + assertTrue(cs.getConcept().isEmpty()); + assertEquals(CodeSystemContentMode.NOTPRESENT, cs.getContent()); + } + + @ParameterizedTest + @ValueSource(strings = { EncodingEnum.JSON_PLAIN_STRING, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_JSON }) + public void testValidateCode_withMimetypesValueSetWithStandardCode_returnsValid(String code) { + // test + IValidationSupport.CodeValidationResult result = mySvc.validateCode(newSupport(), newOptions(), MIMETYPES_CODESYSTEM_URL, code, null, MIMETYPES_VALUESET_URL); + + // verify + assertNotNull(result); + assertEquals(code, result.getCode()); + assertTrue(result.isOk()); + assertNull(result.getSeverity()); + assertNull(result.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { EncodingEnum.JSON_PLAIN_STRING, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_JSON }) + public void testValidateCode_withMimetypesValueSetWithInferSystemWithStandardCode_returnsValid(String code) { + // test + IValidationSupport.CodeValidationResult result = mySvc.validateCode(newSupport(), newOptions().setInferSystem(true), null, code, null, MIMETYPES_VALUESET_URL); + + // verify + assertNotNull(result); + assertEquals(code, result.getCode()); + assertTrue(result.isOk()); + assertNull(result.getSeverity()); + assertNull(result.getMessage()); + } + + @ParameterizedTest + @ValueSource(strings = { EncodingEnum.JSON_PLAIN_STRING, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_JSON }) + public void testValidateCode_withMimetypesWithStandardCode_returnsValid(String code) { + // test + IValidationSupport.CodeValidationResult result = mySvc.validateCode(newSupport(), newOptions(), MIMETYPES_CODESYSTEM_URL, code, null, null); + + // verify + assertNotNull(result); + assertEquals(code, result.getCode()); + assertTrue(result.isOk()); + assertNull(result.getSeverity()); + assertNull(result.getMessage()); + } + + @Test + public void testValidateCode_withMimetypeValueSetWithArbitraryCode_returnsValid() { + // setup + final String code = "someCode"; + final String display = "displayValue"; + + // test + IValidationSupport.CodeValidationResult result = mySvc.validateCode(newSupport(), newOptions(), MIMETYPES_CODESYSTEM_URL, code, display, MIMETYPES_VALUESET_URL); + + // verify + assertNotNull(result); + assertEquals(code, result.getCode()); + assertTrue(result.isOk()); + assertEquals(display, result.getDisplay()); + } + + @Test + public void testValidateCode_withMimetypesWithArbitraryCode_returnsValid() { + // setup + final String code = "someCode"; + final String display = "displayValue"; + + // test + IValidationSupport.CodeValidationResult result = mySvc.validateCode(newSupport(), newOptions(), MIMETYPES_CODESYSTEM_URL, code, display, null); + + // verify + assertNotNull(result); + assertEquals(code, result.getCode()); + assertTrue(result.isOk()); + } + + @ParameterizedTest + @ValueSource(strings = { EncodingEnum.JSON_PLAIN_STRING, Constants.FORMAT_TURTLE, Constants.CT_FHIR_JSON_NEW, Constants.CT_FHIR_JSON }) + public void testLookupCode_withMimetypesWithStandardCode_returnFound(String code) { + // setup + final String system = MIMETYPES_CODESYSTEM_URL; + + // test + IValidationSupport.LookupCodeResult result = mySvc.lookupCode(newSupport(), new LookupCodeRequest(system, code)); + + // verify + assertNotNull(result); + assertEquals(system, result.getSearchedForSystem()); + assertEquals(code, result.getSearchedForCode()); + assertTrue(result.isFound()); + } + + @Test + public void testLookupCode_withMimetypesWithArbitraryCode_returnsFound() { + // setup + final String system = MIMETYPES_CODESYSTEM_URL; + final String code = "someCode"; + + // test + IValidationSupport.LookupCodeResult result = mySvc.lookupCode(newSupport(), new LookupCodeRequest(system, code)); + + // verify + assertNotNull(result); + assertEquals(system, result.getSearchedForSystem()); + assertEquals(code, result.getSearchedForCode()); + assertTrue(result.isFound()); + assertNull(result.getCodeDisplay()); + } + private ValidationSupportContext newSupport() { return new ValidationSupportContext(myCtx.getValidationSupport()); } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java index 77d309503b67..1b5de1d4a50e 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupportTest.java @@ -17,8 +17,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.HashMap; import java.util.Map; @@ -33,10 +31,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class InMemoryTerminologyServerValidationSupportTest extends BaseValidationTestWithInlineMocks { - - private static final Logger ourLog = LoggerFactory.getLogger(InMemoryTerminologyServerValidationSupportTest.class); private InMemoryTerminologyServerValidationSupport mySvc; - private FhirContext myCtx = FhirContext.forR4(); + private final FhirContext myCtx = FhirContext.forR4(); private DefaultProfileValidationSupport myDefaultSupport; private ValidationSupportChain myChain; private PrePopulatedValidationSupport myPrePopulated; @@ -54,8 +50,153 @@ public void before() { myDefaultSupport.fetchCodeSystem("http://foo"); } + @ParameterizedTest + @ValueSource(strings = { + CommonCodeSystemsTerminologyService.MIMETYPES_VALUESET_URL, + CommonCodeSystemsTerminologyService.CURRENCIES_VALUESET_URL, + CommonCodeSystemsTerminologyService.LANGUAGES_VALUESET_URL + }) + public void testExpandValueSet_commonVS_expandOk(String theValueSet) { + ValueSet vs = (ValueSet) myChain.fetchValueSet(theValueSet); + assertNotNull(vs); + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + + IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs); + assertNotNull(expansion); + assertNull(expansion.getError()); + ValueSet valueSet = (ValueSet) expansion.getValueSet(); + assertNotNull(valueSet); + assertNotNull(valueSet.getExpansion()); + } + + + @ParameterizedTest + @ValueSource(strings = { + CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL, + CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL, + CommonCodeSystemsTerminologyService.CURRENCIES_CODESYSTEM_URL + }) + public void testExpandValueSet_customVSBasedOnCommonCS_expandOk(String theCodeSystem) { + ValueSet vs = new ValueSet(); + vs.setId("mimetype"); + vs.setUrl("http://example.com/mimetype"); + vs.setVersion("1.0"); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude(); + vsInclude.setSystem(theCodeSystem); + myPrePopulated.addValueSet(vs); + + vs = (ValueSet) myChain.fetchValueSet(vs.getUrl()); + assertNotNull(vs); + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + + IValidationSupport.ValueSetExpansionOutcome expansion = mySvc.expandValueSet(valCtx, new ValueSetExpansionOptions(), vs); + assertNotNull(expansion); + assertNull(expansion.getError()); + ValueSet valueSet = (ValueSet) expansion.getValueSet(); + assertNotNull(valueSet); + assertNotNull(valueSet.getExpansion()); + } + + @Test + public void testValidateCode_mimetypeVSRandomCode_returnsOk() { + final String codeSystem = CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL; + final String valueSetUrl = CommonCodeSystemsTerminologyService.MIMETYPES_VALUESET_URL; + + final String code = "someRandomCode"; + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + ConceptValidationOptions options = new ConceptValidationOptions(); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, codeSystem, code, null, valueSetUrl); + assertNotNull(outcome); + assertTrue(outcome.isOk()); + assertEquals(code, outcome.getCode()); + } + + @Test + public void testValidateCode_customMimetypeVSRandomCode_returnsOk() { + final String codeSystem = CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL; + final String code = "someRandomCode"; + + ValueSet vs = new ValueSet(); + vs.setId("mimetype"); + vs.setUrl("http://example.com/mimetype"); + vs.setVersion("1.0"); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude(); + vsInclude.setSystem(codeSystem); + myPrePopulated.addValueSet(vs); + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + ConceptValidationOptions options = new ConceptValidationOptions(); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, codeSystem, code, null, vs.getUrl()); + assertNotNull(outcome); + assertTrue(outcome.isOk()); + } + + @Test + public void testValidateCode_customMimetypeVSCodeInVS_returnsOk() { + String codeSystem = CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL; + + final String code = "someRandomCode"; + final String display = "Display " + code; + + ValueSet vs = new ValueSet(); + vs.setId("example-vs"); + vs.setUrl("http://example.com/example-vs"); + vs.setVersion("1.0"); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude(); + vsInclude.setSystem(codeSystem); + vsInclude.addConcept().setCode(code).setDisplay(display); + myPrePopulated.addValueSet(vs); + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + ConceptValidationOptions options = new ConceptValidationOptions(); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, codeSystem, code, null, vs.getUrl()); + assertNotNull(outcome); + assertTrue(outcome.isOk()); + assertEquals(code, outcome.getCode()); + } + + @ParameterizedTest + @ValueSource(strings = { + CommonCodeSystemsTerminologyService.MIMETYPES_CODESYSTEM_URL, + CommonCodeSystemsTerminologyService.COUNTRIES_CODESYSTEM_URL, + CommonCodeSystemsTerminologyService.CURRENCIES_VALUESET_URL, + CommonCodeSystemsTerminologyService.LANGUAGES_CODESYSTEM_URL, + CommonCodeSystemsTerminologyService.UCUM_CODESYSTEM_URL + }) + public void testValidateCode_customMimetypeVSCodeNotInVS_returnsError(String theCodeSystem) { + final String code = "someRandomCode"; + final String codeToValidate = "otherCode"; + + ValueSet vs = new ValueSet(); + vs.setId("mimetype"); + vs.setUrl("http://example.com/mimetype"); + vs.setVersion("1.0"); + vs.setStatus(Enumerations.PublicationStatus.ACTIVE); + ValueSet.ConceptSetComponent vsInclude = vs.getCompose().addInclude(); + vsInclude.setSystem(theCodeSystem); + vsInclude.addConcept().setCode(code).setDisplay("Display " + code); + myPrePopulated.addValueSet(vs); + + ValidationSupportContext valCtx = new ValidationSupportContext(myChain); + ConceptValidationOptions options = new ConceptValidationOptions(); + + IValidationSupport.CodeValidationResult outcome = mySvc.validateCode(valCtx, options, theCodeSystem, codeToValidate, null, vs.getUrl()); + assertNotNull(outcome); + assertFalse(outcome.isOk()); + assertEquals("Unknown code '" + theCodeSystem + "#" + codeToValidate + "' for in-memory expansion of ValueSet '" + vs.getUrl() + "'", outcome.getMessage()); + } + @Test - public void testValidateCodeWithInferredSystem_CommonCodeSystemsCs_BuiltInVs() { + public void testValidateCodeWithInferredSystem_CommonCs_BuiltInVs() { ValidationSupportContext valCtx = new ValidationSupportContext(myChain); ConceptValidationOptions options = new ConceptValidationOptions().setInferSystem(true); @@ -279,9 +420,6 @@ public void testExpandValueSet_VsIsEnumeratedWithVersionedSystem_CsOnlyDifferent assertEquals(null, outcome.getCodeSystemVersion()); } - - - @Test public void testExpandValueSet_VsUsesVersionedSystem_CsIsFragmentWithoutCode() { CodeSystem cs = new CodeSystem(); From 500490761bdbd5a800380dce3de5af7b4dde7542 Mon Sep 17 00:00:00 2001 From: volodymyr-korzh <132366313+volodymyr-korzh@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:17:49 -0700 Subject: [PATCH 10/24] $expunge operation ignoring ExpungeThreadCount setting in certain cases (#5637) * $expunge operation ignoring ExpungeThreadCount setting in certain cases - implementation --- ...oparation-ignore-thread-count-setting.yaml | 7 ++++ .../jpa/dao/expunge/PartitionRunnerTest.java | 35 ++++++++++++++++--- .../fhir/jpa/dao/expunge/PartitionRunner.java | 6 +++- 3 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5636-fix-expunge-oparation-ignore-thread-count-setting.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5636-fix-expunge-oparation-ignore-thread-count-setting.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5636-fix-expunge-oparation-ignore-thread-count-setting.yaml new file mode 100644 index 000000000000..e9ecac200569 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5636-fix-expunge-oparation-ignore-thread-count-setting.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 5636 +jira: SMILE-7648 +title: "Previously, the number of threads allocated to the $expunge operation in certain cases could be more +than configured, this would cause hundreds of threads to be created and all available database connections +to be consumed. This has been fixed." diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java index 4fa1dcf9ad34..73dff282646f 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunnerTest.java @@ -18,7 +18,8 @@ import java.util.function.Consumer; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.isOneOf; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @@ -98,10 +99,10 @@ public void tenItemsBatch5() throws InterruptedException { getPartitionRunner(5).runInPartitionedThreads(resourceIds, partitionConsumer); List calls = myLatch.awaitExpected(); PartitionCall partitionCall1 = (PartitionCall) PointcutLatch.getLatchInvocationParameter(calls, 0); - assertThat(partitionCall1.threadName, isOneOf(TEST_THREADNAME_1, TEST_THREADNAME_2)); + assertThat(partitionCall1.threadName, is(oneOf(TEST_THREADNAME_1, TEST_THREADNAME_2))); assertEquals(5, partitionCall1.size); PartitionCall partitionCall2 = (PartitionCall) PointcutLatch.getLatchInvocationParameter(calls, 1); - assertThat(partitionCall2.threadName, isOneOf(TEST_THREADNAME_1, TEST_THREADNAME_2)); + assertThat(partitionCall2.threadName, is(oneOf(TEST_THREADNAME_1, TEST_THREADNAME_2))); assertEquals(5, partitionCall2.size); assertNotEquals(partitionCall1.threadName, partitionCall2.threadName); } @@ -119,14 +120,38 @@ public void nineItemsBatch5() throws InterruptedException { getPartitionRunner(5).runInPartitionedThreads(resourceIds, partitionConsumer); List calls = myLatch.awaitExpected(); PartitionCall partitionCall1 = (PartitionCall) PointcutLatch.getLatchInvocationParameter(calls, 0); - assertThat(partitionCall1.threadName, isOneOf(TEST_THREADNAME_1, TEST_THREADNAME_2)); + assertThat(partitionCall1.threadName, is(oneOf(TEST_THREADNAME_1, TEST_THREADNAME_2))); assertEquals(true, nums.remove(partitionCall1.size)); PartitionCall partitionCall2 = (PartitionCall) PointcutLatch.getLatchInvocationParameter(calls, 1); - assertThat(partitionCall2.threadName, isOneOf(TEST_THREADNAME_1, TEST_THREADNAME_2)); + assertThat(partitionCall2.threadName, is(oneOf(TEST_THREADNAME_1, TEST_THREADNAME_2))); assertEquals(true, nums.remove(partitionCall2.size)); assertNotEquals(partitionCall1.threadName, partitionCall2.threadName); } + + + /** + * See #5636 $expunge operation ignoring ExpungeThreadCount setting in certain cases + */ + @Test + public void testExpunge_withTasksSizeBiggerThanExecutorQueue_usesConfiguredNumberOfThreads() throws InterruptedException { + // setup + List resourceIds = buildPidList(2500); + Consumer> partitionConsumer = buildPartitionConsumer(myLatch); + // with batch size = 2 we expect 2500/2 runnableTasks to be created + myLatch.setExpectedCount(1250); + + // execute + getPartitionRunner(2, 2).runInPartitionedThreads(resourceIds, partitionConsumer); + List calls = myLatch.awaitExpected(); + + // validate - only two threads should be used for execution + for (int i = 0; i < 1250; i++) { + PartitionCall partitionCall = (PartitionCall) PointcutLatch.getLatchInvocationParameter(calls, i); + assertThat(partitionCall.threadName, is(oneOf(TEST_THREADNAME_1, TEST_THREADNAME_2))); + } + } + @Test public void tenItemsOneThread() throws InterruptedException { List resourceIds = buildPidList(10); diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java index 4f057ac8c972..98e2d5cceb48 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/expunge/PartitionRunner.java @@ -184,9 +184,13 @@ private ExecutorService buildExecutor(int numberOfTasks) { } ourLog.info("Slot become available after {}ms", sw.getMillis()); }; + + // setting corePoolSize and maximumPoolSize to be the same as threadCount + // to ensure that the number of allocated threads for the expunge operation does not exceed the configured limit + // see ThreadPoolExecutor documentation for details return new ThreadPoolExecutor( threadCount, - MAX_POOL_SIZE, + threadCount, 0L, TimeUnit.MILLISECONDS, executorQueue, From 94e932c9a5ed416a3455a98eb04e9edf276e299d Mon Sep 17 00:00:00 2001 From: JP Date: Tue, 30 Jan 2024 09:18:00 -0700 Subject: [PATCH 11/24] Fix Measure group id null pointer exception (#5620) * Update to new version of clinical reasoning, includes a fix for null pointers on Measure group id * add changelog --------- Co-authored-by: Justin McKelvy <60718638+Capt-Mac@users.noreply.github.com> --- .../7_0_0/5640-measurescore-popid-nullpointer-bug.yaml | 5 +++++ pom.xml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5640-measurescore-popid-nullpointer-bug.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5640-measurescore-popid-nullpointer-bug.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5640-measurescore-popid-nullpointer-bug.yaml new file mode 100644 index 000000000000..7f7d57783322 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5640-measurescore-popid-nullpointer-bug.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5640 +jira: SMILE-7977 +title: "Clinical reasoning version bump to address reported 'null pointer' error that is encountered when running $evaluate-measure against a measure with an omitted measure.group.population.id" diff --git a/pom.xml b/pom.xml index 0564e9040dbe..13f2883147e9 100644 --- a/pom.xml +++ b/pom.xml @@ -993,7 +993,7 @@ 1.0.8 - 3.0.0-PRE14 + 3.0.0-PRE15 5.4.1 From 16aa9fcdf23255c1b5aaa1209dc7458e7c6ff87d Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Tue, 30 Jan 2024 14:23:27 -0500 Subject: [PATCH 12/24] Rule evaluation: Allow Bundles with PATCH Parameters (#5641) * Remove parameters clause. * Finalize changelog. Add tests. Finalize implementation. * Undo changes to this test. * Revert all changes to FhirQueryRuleImplTest. Add new RuleImplOpTest. Ensure that proper nested Bundle is created for test and other fixes. * Tweak test. * Use real rule applier in test and remove all mocks. --- ...t-handle-transaction-nested-paramters.yaml | 5 + .../ca/uhn/fhir/jpa/auth/RuleImplOpTest.java | 135 ++++++++++++++++++ .../server/interceptor/auth/RuleImplOp.java | 27 +++- 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5642-bundle-patch-cant-handle-transaction-nested-paramters.yaml create mode 100644 hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/auth/RuleImplOpTest.java diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5642-bundle-patch-cant-handle-transaction-nested-paramters.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5642-bundle-patch-cant-handle-transaction-nested-paramters.yaml new file mode 100644 index 000000000000..129e62825236 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5642-bundle-patch-cant-handle-transaction-nested-paramters.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5642 +title: "A non-superuser with correct permissions encounters HAPI-0339 when POSTING a transaction Bundle with a PATCH. + This has been fixed." diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/auth/RuleImplOpTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/auth/RuleImplOpTest.java new file mode 100644 index 000000000000..0bfb332f5802 --- /dev/null +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/auth/RuleImplOpTest.java @@ -0,0 +1,135 @@ +package ca.uhn.fhir.jpa.auth; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.interceptor.auth.AuthorizationInterceptor; +import ca.uhn.fhir.rest.server.interceptor.auth.IAuthRule; +import ca.uhn.fhir.rest.server.interceptor.auth.IRuleApplier; +import ca.uhn.fhir.rest.server.interceptor.auth.PolicyEnum; +import ca.uhn.fhir.rest.server.interceptor.auth.RuleBuilder; +import ca.uhn.fhir.util.BundleBuilder; +import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.StringType; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class RuleImplOpTest { + private static final String OPERATION = "operation"; + private static final String TYPE = "type"; + private static final String PATH = "path"; + private static final String VALUE = "value"; + private static final String REPLACE = "replace"; + private static final String PATIENT_BIRTH_DATE = "Patient.birthDate"; + private static final Parameters PARAMETERS = buildParameters(); + private static final String DOCUMENT = "document"; + private static final String ERROR_TEMPLATE = "HAPI-0339: Can not handle transaction with nested resource of type %s"; + private static final String ERROR_PARAMETERS = String.format(ERROR_TEMPLATE, "Parameters"); + private static final String ERROR_BUNDLE = String.format(ERROR_TEMPLATE, "Bundle"); + + private static final String REQUEST_RULELIST = AuthorizationInterceptor.class.getName() + "_1_RULELIST"; + private final Patient myPatient = buildPatient(); + + private final List myRules = new RuleBuilder() + .allow() + .transaction() + .withAnyOperation() + .andApplyNormalRules() + .andThen() + .allow() + .write() + .allResources() + .withAnyId() + .build(); + + private final IAuthRule myRule = myRules.get(0); + private final FhirContext myFhirContext = FhirContext.forR4Cached(); + private final IBaseBundle myInnerBundle = buildInnerBundler(myFhirContext); + + private final RequestDetails mySystemRequestDetails = buildSystemRequestDetails(myFhirContext, myRules); + private final IRuleApplier myRuleApplier = new AuthorizationInterceptor(); + + @Test + void testTransactionBundleUpdateWithParameters() { + final BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); + bundleBuilder.addTransactionUpdateEntry(PARAMETERS); + + try { + applyRule(bundleBuilder.getBundle()); + fail("Expected an InvalidRequestException"); + } catch (InvalidRequestException exception) { + assertEquals(ERROR_PARAMETERS, exception.getMessage()); + } + } + + @Test + void testTransactionBundleWithNestedBundle() { + final BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); + bundleBuilder.addTransactionCreateEntry(myInnerBundle); + + try { + applyRule(bundleBuilder.getBundle()); + fail("Expected an InvalidRequestException"); + } catch (InvalidRequestException exception) { + assertEquals(ERROR_BUNDLE, exception.getMessage()); + } + } + + @Test + void testTransactionBundlePatchWithParameters() { + final BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); + bundleBuilder.addTransactionFhirPatchEntry(myPatient.getIdElement(), PARAMETERS); + + final AuthorizationInterceptor.Verdict verdict = applyRule(bundleBuilder.getBundle()); + + assertThat(verdict.getDecision(), equalTo(PolicyEnum.ALLOW)); + } + + private AuthorizationInterceptor.Verdict applyRule(IBaseBundle theBundle) { + return myRule.applyRule(RestOperationTypeEnum.TRANSACTION, mySystemRequestDetails, theBundle, myPatient.getIdElement(), myPatient, myRuleApplier, new HashSet<>(), null); + } + + private static Parameters buildParameters() { + final Parameters patch = new Parameters(); + + final Parameters.ParametersParameterComponent op = patch.addParameter().setName(OPERATION); + op.addPart().setName(TYPE).setValue(new CodeType(REPLACE)); + op.addPart().setName(PATH).setValue(new CodeType(PATIENT_BIRTH_DATE)); + op.addPart().setName(VALUE).setValue(new StringType("1912-04-14")); + + return patch; + } + + private static RequestDetails buildSystemRequestDetails(FhirContext theFhirContext, List theRules) { + final SystemRequestDetails systemRequestDetails = new SystemRequestDetails(); + systemRequestDetails.setFhirContext(theFhirContext); + systemRequestDetails.getUserData().put(REQUEST_RULELIST, theRules); + + return systemRequestDetails; + } + + private static Patient buildPatient() { + final Patient patient = new Patient(); + patient.setId(new IdType("Patient", "1")); + return patient; + } + + private static IBaseBundle buildInnerBundler(FhirContext theFhirContext) { + final BundleBuilder innerBundleBuilder = new BundleBuilder(theFhirContext); + innerBundleBuilder.setType(DOCUMENT); + return innerBundleBuilder.getBundle(); + } +} diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index 460796e91214..325ebd6e8496 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -63,6 +63,8 @@ @SuppressWarnings("EnumSwitchStatementWhichMissesCases") class RuleImplOp extends BaseRule /* implements IAuthRule */ { private static final Logger ourLog = LoggerFactory.getLogger(RuleImplOp.class); + private static final String PARAMETERS = "Parameters"; + private static final String BUNDLE = "Bundle"; private AppliesTypeEnum myAppliesTo; private Set myAppliesToTypes; @@ -771,7 +773,10 @@ private Verdict applyRuleToTransaction( */ if (nextPart.getResource() != null) { RuntimeResourceDefinition resourceDef = ctx.getResourceDefinition(nextPart.getResource()); - if ("Parameters".equals(resourceDef.getName()) || "Bundle".equals(resourceDef.getName())) { + + // TODO: LD: We should pursue a more ideal fix after the release to inspect the bundle more deeply + // to ensure that it's a valid request + if (shouldRejectBundleEntry(resourceDef, operation)) { throw new InvalidRequestException(Msg.code(339) + "Can not handle transaction with nested resource of type " + resourceDef.getName()); } @@ -835,6 +840,24 @@ private Verdict applyRuleToTransaction( } } + /** + * Ascertain whether this transaction request contains a nested operations or nested transactions. + * This is done carefully because a bundle can contain a nested PATCH with Parameters, which is supported but + * a non-PATCH nested Parameters resource may be problematic. + * + * @param theResourceDef The {@link RuntimeResourceDefinition} associated with this bundle entry + * @param theOperation The {@link RestOperationTypeEnum} associated with this bundle entry + * @return true if we should reject this reject + */ + private boolean shouldRejectBundleEntry( + RuntimeResourceDefinition theResourceDef, RestOperationTypeEnum theOperation) { + final boolean isResourceParameters = PARAMETERS.equals(theResourceDef.getName()); + final boolean isResourceBundle = BUNDLE.equals(theResourceDef.getName()); + final boolean isOperationPatch = theOperation == RestOperationTypeEnum.PATCH; + + return (isResourceParameters && !isOperationPatch) || isResourceBundle; + } + private void setTargetFromResourceId(RequestDetails theRequestDetails, FhirContext ctx, RuleTarget target) { String[] idValues = theRequestDetails.getParameters().get(SP_RES_ID); target.resourceIds = new ArrayList<>(); @@ -909,7 +932,7 @@ public void setTransactionAppliesToOp(TransactionAppliesToEnum theOp) { private boolean requestAppliesToTransaction( FhirContext theContext, RuleOpEnum theOp, IBaseResource theInputResource) { - if (!"Bundle".equals(theContext.getResourceType(theInputResource))) { + if (!BUNDLE.equals(theContext.getResourceType(theInputResource))) { return false; } From 5ed30f3181bf552dab4bf414cacabaa451b2d08d Mon Sep 17 00:00:00 2001 From: jmarchionatto <60409882+jmarchionatto@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:08:50 -0500 Subject: [PATCH 13/24] =?UTF-8?q?Prevent=20batch2=20job=20execution=20to?= =?UTF-8?q?=20stop=20for=20empty=20chunk=20when=20last=20job=20st=E2=80=A6?= =?UTF-8?q?=20(#5635)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent batch2 job execution to stop for empty chunk when last job step is a reduction. Add output to bulk export result even when empty. * Fix test * Unimportant change to force fresh build * Implement review suggestions --------- Co-authored-by: juan.marchionatto --- ...rt-response-must-have-required-fields.yaml | 6 + .../fhir/jpa/bulk/BulkExportUseCaseTest.java | 105 +++++++++++++++--- .../jobs/export/BulkDataExportProvider.java | 3 + .../batch2/coordinator/JobStepExecutor.java | 5 +- .../uhn/fhir/batch2/model/JobDefinition.java | 9 +- .../batch2/progress/InstanceProgress.java | 4 +- .../JobInstanceProgressCalculator.java | 22 +++- .../ReductionStepDataSinkTest.java | 16 ++- 8 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5632-bulk-export-response-must-have-required-fields.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5632-bulk-export-response-must-have-required-fields.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5632-bulk-export-response-must-have-required-fields.yaml new file mode 100644 index 000000000000..cc207611c3f3 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5632-bulk-export-response-must-have-required-fields.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 5632 +title: "Previously bulk export operation was returning an empty response when no resources matched the request, which + didn't comply with [HL7 HAPI IG](https://hl7.org/fhir/uv/bulkdata/export/index.html#response---complete-status). + This has been corrected." diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkExportUseCaseTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkExportUseCaseTest.java index 6899c125cb72..88e989e2ebd1 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkExportUseCaseTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/BulkExportUseCaseTest.java @@ -87,6 +87,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -299,9 +300,9 @@ public void export_shouldExportPatientAndObservationAndEncounterResources_whenTy assertThat(result.getRequiresAccessToken(), is(equalTo(true))); assertThat(result.getTransactionTime(), is(notNullValue())); assertEquals(result.getOutput().size(), 3); - assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Patient")).collect(Collectors.toList()).size()); - assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Observation")).collect(Collectors.toList()).size()); - assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Encounter")).collect(Collectors.toList()).size()); + assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Patient")).count()); + assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Observation")).count()); + assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Encounter")).count()); //We assert specifically on content as the deserialized version will "helpfully" fill in missing fields. assertThat(responseContent, containsString("\"error\" : [ ]")); @@ -338,8 +339,8 @@ public void export_shouldNotExportBinaryResource_whenTypeParameterOmitted() thro assertThat(result.getRequiresAccessToken(), is(equalTo(true))); assertThat(result.getTransactionTime(), is(notNullValue())); assertEquals(result.getOutput().size(), 1); - assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Patient")).collect(Collectors.toList()).size()); - assertEquals(0, result.getOutput().stream().filter(o -> o.getType().equals("Binary")).collect(Collectors.toList()).size()); + assertEquals(1, result.getOutput().stream().filter(o -> o.getType().equals("Patient")).count()); + assertEquals(0, result.getOutput().stream().filter(o -> o.getType().equals("Binary")).count()); //We assert specifically on content as the deserialized version will "helpfully" fill in missing fields. assertThat(responseContent, containsString("\"error\" : [ ]")); @@ -381,7 +382,7 @@ public void testBinariesAreStreamedWithRespectToAcceptHeader() throws IOExceptio } HashSet types = Sets.newHashSet("Patient"); - BulkExportJobResults bulkExportJobResults = startSystemBulkExportJobAndAwaitCompletion(types, new HashSet()); + BulkExportJobResults bulkExportJobResults = startSystemBulkExportJobAndAwaitCompletion(types, new HashSet<>()); Map> resourceTypeToBinaryIds = bulkExportJobResults.getResourceTypeToBinaryIds(); assertThat(resourceTypeToBinaryIds.get("Patient"), hasSize(1)); String patientBinaryId = resourceTypeToBinaryIds.get("Patient").get(0); @@ -477,7 +478,7 @@ public void testResourceCountIsCorrect() { String entities = myJobInstanceRepository .findAll() .stream() - .map(t -> t.toString()) + .map(Batch2JobInstanceEntity::toString) .collect(Collectors.joining("\n * ")); ourLog.info("Entities:\n * " + entities); }); @@ -492,6 +493,41 @@ public void testResourceCountIsCorrect() { assertEquals(patientCount, jobInstance.getCombinedRecordsProcessed()); } + @Test + public void testEmptyExport() { + BulkExportJobParameters options = new BulkExportJobParameters(); + options.setResourceTypes(Collections.singleton("Patient")); + options.setFilters(Collections.emptySet()); + options.setExportStyle(BulkExportJobParameters.ExportStyle.SYSTEM); + options.setOutputFormat(Constants.CT_FHIR_NDJSON); + + JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); + startRequest.setJobDefinitionId(Batch2JobDefinitionConstants.BULK_EXPORT); + startRequest.setParameters(options); + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(mySrd, startRequest); + + assertNotNull(startResponse); + + final String jobId = startResponse.getInstanceId(); + + // Run a scheduled pass to build the export + myBatch2JobHelper.awaitJobCompletion(startResponse.getInstanceId()); + runInTransaction(() -> { + String entities = myJobInstanceRepository + .findAll() + .stream() + .map(Batch2JobInstanceEntity::toString) + .collect(Collectors.joining("\n * ")); + ourLog.info("Entities:\n * " + entities); + }); + + final Optional optJobInstance = myJobPersistence.fetchInstance(jobId); + assertNotNull(optJobInstance); + assertTrue(optJobInstance.isPresent()); + assertThat(optJobInstance.get().getReport(), + containsString("Export complete, but no data to generate report for job instance:")); + } + private void logContentTypeAndResponse(Header[] headers, String response) { ourLog.info("**************************"); ourLog.info("Content-Type is: {}", headers[0]); @@ -542,7 +578,7 @@ public void testPatientExportIgnoresResourcesNotInPatientCompartment() { // test HashSet types = Sets.newHashSet("Patient", "Observation"); - BulkExportJobResults bulkExportJobResults = startPatientBulkExportJobAndAwaitResults(types, new HashSet(), "ha"); + BulkExportJobResults bulkExportJobResults = startPatientBulkExportJobAndAwaitResults(types, new HashSet<>(), "ha"); Map> typeToResources = convertJobResultsToResources(bulkExportJobResults); assertThat(typeToResources.get("Patient"), hasSize(1)); assertThat(typeToResources.get("Observation"), hasSize(1)); @@ -605,6 +641,34 @@ public void testBulkExportWithLowMaxFileCapacity() { assertTrue(patientIds.contains(resourceId)); } } + + @Test + public void testExportEmptyResult() { + BulkExportJobParameters options = new BulkExportJobParameters(); + options.setResourceTypes(Sets.newHashSet("Patient")); + options.setExportStyle(BulkExportJobParameters.ExportStyle.PATIENT); + options.setOutputFormat(Constants.CT_FHIR_NDJSON); + + JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); + startRequest.setJobDefinitionId(Batch2JobDefinitionConstants.BULK_EXPORT); + startRequest.setParameters(options); + Batch2JobStartResponse job = myJobCoordinator.startInstance(mySrd, startRequest); + myBatch2JobHelper.awaitJobCompletion(job.getInstanceId(), 60); + ourLog.debug("Job status after awaiting - {}", myJobCoordinator.getInstance(job.getInstanceId()).getStatus()); + await() + .atMost(300, TimeUnit.SECONDS) + .until(() -> { + StatusEnum status = myJobCoordinator.getInstance(job.getInstanceId()).getStatus(); + if (!StatusEnum.COMPLETED.equals(status)) { + fail("Job status was changed from COMPLETE to " + status); + } + return myJobCoordinator.getInstance(job.getInstanceId()).getReport() != null; + }); + + String report = myJobCoordinator.getInstance(job.getInstanceId()).getReport(); + assertThat(report, + containsString("Export complete, but no data to generate report for job instance:")); + } } @@ -1081,7 +1145,7 @@ public void testGroupBulkExportWithTypeFilter_ReturnsOnlyResourcesInTypeFilter() } } ] - } + } """; Bundle bundle = parser.parseResource(Bundle.class, bundleStr); myClient.transaction().withBundle(bundle).execute(); @@ -1218,6 +1282,21 @@ public void testGroupExportPatientOnly() { assertThat(typeToContents.get("Patient"), not(containsString("POG2"))); } + @Test + public void testExportEmptyResult() { + Group group = new Group(); + group.setId("Group/G-empty"); + group.setActive(true); + myClient.update().resource(group).execute(); + + HashSet resourceTypes = Sets.newHashSet("Patient"); + BulkExportJobResults bulkExportJobResults = startGroupBulkExportJobAndAwaitCompletion( + resourceTypes, new HashSet<>(), "G-empty"); + + assertThat(bulkExportJobResults.getReportMsg(), + startsWith("Export complete, but no data to generate report for job instance:")); + } + @Test public void testGroupBulkExportMultipleResourceTypes() { Patient patient = new Patient(); @@ -1398,7 +1477,7 @@ private Map convertJobResultsToStringContents(BulkExportJobResul Map> convertJobResultsToResources(BulkExportJobResults theResults) { Map stringStringMap = convertJobResultsToStringContents(theResults); Map> typeToResources = new HashMap<>(); - stringStringMap.entrySet().forEach(entry -> typeToResources.put(entry.getKey(), convertNDJSONToResources(entry.getValue()))); + stringStringMap.forEach((key, value) -> typeToResources.put(key, convertNDJSONToResources(value))); return typeToResources; } @@ -1412,8 +1491,7 @@ private List convertNDJSONToResources(String theValue) { private String getBinaryContentsAsString(String theBinaryId) { Binary binary = myBinaryDao.read(new IdType(theBinaryId)); assertEquals(Constants.CT_FHIR_NDJSON, binary.getContentType()); - String contents = new String(binary.getContent(), Constants.CHARSET_UTF8); - return contents; + return new String(binary.getContent(), Constants.CHARSET_UTF8); } BulkExportJobResults startGroupBulkExportJobAndAwaitCompletion(HashSet theResourceTypes, HashSet theFilters, String theGroupId) { @@ -1509,8 +1587,7 @@ BulkExportJobResults startBulkExportJobAndAwaitCompletion( await().atMost(300, TimeUnit.SECONDS).until(() -> myJobCoordinator.getInstance(jobInstanceId).getReport() != null); String report = myJobCoordinator.getInstance(jobInstanceId).getReport(); - BulkExportJobResults results = JsonUtil.deserialize(report, BulkExportJobResults.class); - return results; + return JsonUtil.deserialize(report, BulkExportJobResults.class); } private void verifyBulkExportResults(String theGroupId, HashSet theFilters, List theContainedList, List theExcludedList) { diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java index 686297507405..1df5e286a99c 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java @@ -502,6 +502,9 @@ public void exportPollStatus( String serverBase = getServerBase(theRequestDetails); + // an output is required, even if empty, according to HL7 FHIR IG + bulkResponseDocument.getOutput(); + for (Map.Entry> entrySet : results.getResourceTypeToBinaryIds().entrySet()) { String resourceType = entrySet.getKey(); diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobStepExecutor.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobStepExecutor.java index b0ae258fd272..a39dccb9a757 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobStepExecutor.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/coordinator/JobStepExecutor.java @@ -75,9 +75,10 @@ public void executeStep() { return; } - if (stepExecutorOutput.getDataSink().firstStepProducedNothing()) { + if (stepExecutorOutput.getDataSink().firstStepProducedNothing() && !myDefinition.isLastStepReduction()) { ourLog.info( - "First step of job myInstance {} produced no work chunks, marking as completed and setting end date", + "First step of job myInstance {} produced no work chunks and last step is not a reduction, " + + "marking as completed and setting end date", myInstanceId); myJobPersistence.updateInstance(myInstance.getInstanceId(), instance -> { instance.setEndTime(new Date()); diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobDefinition.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobDefinition.java index 29ac2faf4f5c..d10ca861a3f6 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobDefinition.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobDefinition.java @@ -145,6 +145,11 @@ public boolean isGatedExecution() { return myGatedExecution; } + public boolean isLastStepReduction() { + int stepCount = getSteps().size(); + return stepCount >= 1 && getSteps().get(stepCount - 1).isReductionStep(); + } + public int getStepIndex(String theStepId) { int retVal = myStepIds.indexOf(theStepId); Validate.isTrue(retVal != -1); @@ -304,9 +309,9 @@ public Builder addFinalReducerStep( throw new ConfigurationException(Msg.code(2106) + String.format("Job Definition %s has a reducer step but is not gated", myJobDefinitionId)); } - mySteps.add(new JobDefinitionReductionStep( + mySteps.add(new JobDefinitionReductionStep<>( theStepId, theStepDescription, theStepWorker, myNextInputType, theOutputType)); - return new Builder( + return new Builder<>( mySteps, myJobDefinitionId, myJobDefinitionVersion, diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/InstanceProgress.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/InstanceProgress.java index a21d6c595e2b..790ed970c1ae 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/InstanceProgress.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/InstanceProgress.java @@ -192,12 +192,12 @@ private int getChunkCount() { /** * Transitions from IN_PROGRESS/ERRORED based on chunk statuses. */ - public void calculateNewStatus() { + public void calculateNewStatus(boolean theLastStepIsReduction) { if (myFailedChunkCount > 0) { myNewStatus = StatusEnum.FAILED; } else if (myErroredChunkCount > 0) { myNewStatus = StatusEnum.ERRORED; - } else if (myIncompleteChunkCount == 0 && myCompleteChunkCount > 0) { + } else if (myIncompleteChunkCount == 0 && myCompleteChunkCount > 0 && !theLastStepIsReduction) { myNewStatus = StatusEnum.COMPLETED; } } diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/JobInstanceProgressCalculator.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/JobInstanceProgressCalculator.java index e5ce87c8a589..348fd30e5404 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/JobInstanceProgressCalculator.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/progress/JobInstanceProgressCalculator.java @@ -22,19 +22,26 @@ import ca.uhn.fhir.batch2.api.IJobPersistence; import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry; import ca.uhn.fhir.batch2.maintenance.JobChunkProgressAccumulator; +import ca.uhn.fhir.batch2.model.JobDefinition; +import ca.uhn.fhir.batch2.model.JobInstance; import ca.uhn.fhir.batch2.model.WorkChunk; +import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.model.api.IModelJson; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.Logs; import ca.uhn.fhir.util.StopWatch; import jakarta.annotation.Nonnull; import org.slf4j.Logger; import java.util.Iterator; +import java.util.Optional; public class JobInstanceProgressCalculator { private static final Logger ourLog = Logs.getBatchTroubleshootingLog(); private final IJobPersistence myJobPersistence; private final JobChunkProgressAccumulator myProgressAccumulator; private final JobInstanceStatusUpdater myJobInstanceStatusUpdater; + private final JobDefinitionRegistry myJobDefinitionRegistry; public JobInstanceProgressCalculator( IJobPersistence theJobPersistence, @@ -42,6 +49,7 @@ public JobInstanceProgressCalculator( JobDefinitionRegistry theJobDefinitionRegistry) { myJobPersistence = theJobPersistence; myProgressAccumulator = theProgressAccumulator; + myJobDefinitionRegistry = theJobDefinitionRegistry; myJobInstanceStatusUpdater = new JobInstanceStatusUpdater(theJobDefinitionRegistry); } @@ -96,8 +104,20 @@ public InstanceProgress calculateInstanceProgress(String instanceId) { } // wipmb separate status update from stats collection in 6.8 - instanceProgress.calculateNewStatus(); + instanceProgress.calculateNewStatus(lastStepIsReduction(instanceId)); return instanceProgress; } + + private boolean lastStepIsReduction(String theInstanceId) { + JobInstance jobInstance = getJobInstance(theInstanceId); + JobDefinition jobDefinition = myJobDefinitionRegistry.getJobDefinitionOrThrowException(jobInstance); + return jobDefinition.isLastStepReduction(); + } + + private JobInstance getJobInstance(String theInstanceId) { + Optional oInstance = myJobPersistence.fetchInstance(theInstanceId); + return oInstance.orElseThrow(() -> + new InternalErrorException(Msg.code(2486) + "Failed to fetch JobInstance with id: " + theInstanceId)); + } } diff --git a/hapi-fhir-storage-batch2/src/test/java/ca/uhn/fhir/batch2/coordinator/ReductionStepDataSinkTest.java b/hapi-fhir-storage-batch2/src/test/java/ca/uhn/fhir/batch2/coordinator/ReductionStepDataSinkTest.java index 425f1d4b66e4..28d246ccd4fb 100644 --- a/hapi-fhir-storage-batch2/src/test/java/ca/uhn/fhir/batch2/coordinator/ReductionStepDataSinkTest.java +++ b/hapi-fhir-storage-batch2/src/test/java/ca/uhn/fhir/batch2/coordinator/ReductionStepDataSinkTest.java @@ -22,6 +22,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.util.Collections; +import java.util.Optional; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -30,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -90,12 +92,16 @@ public void accept_validInputSubmittedOnlyOnce_updatesInstanceWithData() { String data = "data"; StepOutputData stepData = new StepOutputData(data); WorkChunkData chunkData = new WorkChunkData<>(stepData); + @SuppressWarnings("unchecked") + JobDefinition jobDefinition = mock(JobDefinition.class); // when JobInstance instance = JobInstance.fromInstanceId(INSTANCE_ID); instance.setStatus(StatusEnum.FINALIZE); stubUpdateInstanceCallback(instance); when(myJobPersistence.fetchAllWorkChunksIterator(any(), anyBoolean())).thenReturn(Collections.emptyIterator()); + when(myJobPersistence.fetchInstance(INSTANCE_ID)).thenReturn(Optional.of(instance)); + when(myJobDefinitionRegistry.getJobDefinitionOrThrowException(instance)).thenReturn(jobDefinition); // test myDataSink.accept(chunkData); @@ -111,6 +117,8 @@ public void accept_multipleCalls_firstInWins() { String data2 = "data2"; WorkChunkData firstData = new WorkChunkData<>(new StepOutputData(data)); WorkChunkData secondData = new WorkChunkData<>(new StepOutputData(data2)); + @SuppressWarnings("unchecked") + JobDefinition jobDefinition = mock(JobDefinition.class); ourLogger.setLevel(Level.ERROR); @@ -118,6 +126,8 @@ public void accept_multipleCalls_firstInWins() { instance.setStatus(StatusEnum.FINALIZE); when(myJobPersistence.fetchAllWorkChunksIterator(any(), anyBoolean())).thenReturn(Collections.emptyIterator()); stubUpdateInstanceCallback(instance); + when(myJobPersistence.fetchInstance(INSTANCE_ID)).thenReturn(Optional.of(instance)); + when(myJobDefinitionRegistry.getJobDefinitionOrThrowException(instance)).thenReturn(jobDefinition); // test myDataSink.accept(firstData); @@ -136,10 +146,15 @@ private void stubUpdateInstanceCallback(JobInstance theJobInstance) { @Test public void accept_noInstanceIdFound_throwsJobExecutionFailed() { // setup + JobInstance jobInstance = mock(JobInstance.class); + @SuppressWarnings("unchecked") + JobDefinition jobDefinition = (JobDefinition) mock(JobDefinition.class); String data = "data"; WorkChunkData chunkData = new WorkChunkData<>(new StepOutputData(data)); when(myJobPersistence.updateInstance(any(), any())).thenReturn(false); when(myJobPersistence.fetchAllWorkChunksIterator(any(), anyBoolean())).thenReturn(Collections.emptyIterator()); + when(myJobPersistence.fetchInstance(INSTANCE_ID)).thenReturn(Optional.of(jobInstance)); + when(myJobDefinitionRegistry.getJobDefinitionOrThrowException(jobInstance)).thenReturn(jobDefinition); // test try { @@ -151,5 +166,4 @@ public void accept_noInstanceIdFound_throwsJobExecutionFailed() { fail("Unexpected exception", anyOtherEx); } } - } From 59f7d4a9a36a2a7b2a22c4a2bac4c54ea62ef9fe Mon Sep 17 00:00:00 2001 From: Michael Buckley Date: Tue, 30 Jan 2024 20:04:27 -0500 Subject: [PATCH 14/24] Index review fixes (#5649) * Don't hold locks while adding indices during upgrade. * concurrent indexing is non-transactional in PG. --- .../fhir/changelog/7_0_0/5649-index-review.yaml | 4 ++++ .../migrate/tasks/HapiFhirJpaMigrationTasks.java | 14 +++++++++++--- .../ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java | 5 +++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5649-index-review.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5649-index-review.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5649-index-review.yaml new file mode 100644 index 000000000000..bdd4182bde96 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5649-index-review.yaml @@ -0,0 +1,4 @@ +--- +type: fix +issue: 5649 +title: "Change database upgrade script to avoid holding locks while adding indices." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java index 340a6cbee2dc..4046890a9f00 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/HapiFhirJpaMigrationTasks.java @@ -133,10 +133,12 @@ protected void init700() { mdmLinkTable .addIndex("20230911.1", "IDX_EMPI_TGT_MR_LS") .unique(false) + .online(true) .withColumns("TARGET_TYPE", "MATCH_RESULT", "LINK_SOURCE"); mdmLinkTable .addIndex("20230911.2", "IDX_EMPi_TGT_MR_SCore") .unique(false) + .online(true) .withColumns("TARGET_TYPE", "MATCH_RESULT", "SCORE"); // Move forced_id constraints to hfj_resource and the new fhir_id column @@ -166,7 +168,11 @@ protected void init700() { .withColumns("RES_TYPE", "FHIR_ID"); // For resolving references that don't supply the type. - hfjResource.addIndex("20231027.3", "IDX_RES_FHIR_ID").unique(false).withColumns("FHIR_ID"); + hfjResource + .addIndex("20231027.3", "IDX_RES_FHIR_ID") + .unique(false) + .online(true) + .withColumns("FHIR_ID"); Builder.BuilderWithTableName batch2JobInstanceTable = version.onTable("BT2_JOB_INSTANCE"); @@ -177,7 +183,8 @@ protected void init700() { { version.executeRawSql( "20231212.1", - "CREATE INDEX idx_sp_string_hash_nrm_pattern_ops ON public.hfj_spidx_string USING btree (hash_norm_prefix, sp_value_normalized varchar_pattern_ops, res_id, partition_id)") + "CREATE INDEX CONCURRENTLY idx_sp_string_hash_nrm_pattern_ops ON public.hfj_spidx_string USING btree (hash_norm_prefix, sp_value_normalized varchar_pattern_ops, res_id, partition_id)") + .setTransactional(false) .onlyAppliesToPlatforms(DriverTypeEnum.POSTGRES_9_4) .onlyIf( String.format( @@ -190,7 +197,8 @@ protected void init700() { "Index idx_sp_string_hash_nrm_pattern_ops already exists"); version.executeRawSql( "20231212.2", - "CREATE UNIQUE INDEX idx_sp_uri_hash_identity_pattern_ops ON public.hfj_spidx_uri USING btree (hash_identity, sp_uri varchar_pattern_ops, res_id, partition_id)") + "CREATE UNIQUE INDEX CONCURRENTLY idx_sp_uri_hash_identity_pattern_ops ON public.hfj_spidx_uri USING btree (hash_identity, sp_uri varchar_pattern_ops, res_id, partition_id)") + .setTransactional(false) .onlyAppliesToPlatforms(DriverTypeEnum.POSTGRES_9_4) .onlyIf( String.format( diff --git a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java index 39365829c775..387cea1aeb70 100644 --- a/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java +++ b/hapi-fhir-sql-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/tasks/api/Builder.java @@ -614,6 +614,11 @@ public BuilderCompleteTask runEvenDuringSchemaInitialization() { myTask.setRunDuringSchemaInitialization(true); return this; } + + public BuilderCompleteTask setTransactional(boolean theFlag) { + myTask.setTransactional(theFlag); + return this; + } } public class BuilderAddTableRawSql { From ec1b8fe7ee194c0a94aceeb59933537b974efc48 Mon Sep 17 00:00:00 2001 From: Luke deGruchy Date: Wed, 31 Jan 2024 17:20:38 -0500 Subject: [PATCH 15/24] Fix conditional creates without leading '?' (#5646) * First commit with failing test. * More tests and logs. * More logs * Try new solution for BaseTransactionProcessor.performIdSubstitutionsInMatchUrl(). * Simplify solution. Add more tests. * Changelog. * javadoc --- .../java/ca/uhn/fhir/util/BundleBuilder.java | 16 ++- ...itional-create-reference-query-string.yaml | 6 + .../jpa/dao/ResourceHistoryCalculator.java | 19 +++ .../fhir/jpa/dao/ResourceHistoryState.java | 19 +++ .../dao/r4/FhirResourceDaoR4CreateTest.java | 121 ++++++++++++++++++ .../test/BaseTransactionProcessorTest.java | 17 +++ .../jpa/dao/BaseTransactionProcessor.java | 3 +- 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5651-bundle-conditional-create-reference-query-string.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java index 842fd8555089..c5f25cb37448 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleBuilder.java @@ -251,10 +251,22 @@ public void addTransactionUpdateIdOnlyEntry(IBaseResource theResource) { * @param theResource The resource to create */ public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) { + return addTransactionCreateEntry(theResource, null); + } + + /** + * Adds an entry containing an create (POST) request. + * Also sets the Bundle.type value to "transaction" if it is not already set. + * + * @param theResource The resource to create + * @param theFullUrl The fullUrl to attach to the entry. If null, will default to the resource ID. + */ + public CreateBuilder addTransactionCreateEntry(IBaseResource theResource, @Nullable String theFullUrl) { setBundleField("type", "transaction"); - IBase request = - addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue()); + IBase request = addEntryAndReturnRequest( + theResource, + theFullUrl != null ? theFullUrl : theResource.getIdElement().getValue()); String resourceType = myContext.getResourceType(theResource); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5651-bundle-conditional-create-reference-query-string.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5651-bundle-conditional-create-reference-query-string.yaml new file mode 100644 index 000000000000..df8d742600ed --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5651-bundle-conditional-create-reference-query-string.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 5651 +jira: SMILE-7855 +title: "Previously, conditional creates would fail with HAPI-0929 errors if there was no preceding '?'. + This has been fixed." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java index fafa3c4ca293..1114af1a887f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryCalculator.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java index 6da5f9ab5941..bdf5cdfa8eec 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/ResourceHistoryState.java @@ -1,3 +1,22 @@ +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum; diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index db30329cbad9..a4747add0e20 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.entity.NormalizedQuantitySearchLevel; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; @@ -29,6 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.exparity.hamcrest.date.DateMatchers; +import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; @@ -39,6 +41,7 @@ import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Organization; @@ -48,8 +51,12 @@ import org.hl7.fhir.r4.model.SampledData; import org.hl7.fhir.r4.model.SearchParameter; import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.Task; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.PageRequest; @@ -60,6 +67,7 @@ import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -77,6 +85,7 @@ import static org.hamcrest.Matchers.notNullValue; 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.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -1224,4 +1233,116 @@ public void testCreateWithNormalizedQuantitySearchNotSupported_SmallerThanCanoni assertEquals(1, ids.size()); } + @Nested + class ConditionalCreates { + private static final String SYSTEM = "http://tempuri.org"; + private static final String VALUE_1 = "1"; + private static final String VALUE_2 = "2"; + + private final Task myTask1 = new Task() + .setStatus(Task.TaskStatus.DRAFT) + .setIntent(Task.TaskIntent.UNKNOWN) + .addIdentifier(new Identifier() + .setSystem(SYSTEM) + .setValue(VALUE_1)); + + private final Task myTask2 = new Task() + .setStatus(Task.TaskStatus.DRAFT) + .setIntent(Task.TaskIntent.UNKNOWN) + .addIdentifier(new Identifier() + .setSystem(SYSTEM) + .setValue(VALUE_2)) + .addBasedOn(new Reference().setReference("urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea")); + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testConditionalCreateDependsOnPOSTedResource(boolean theHasQuestionMark) { + final IFhirResourceDao taskDao = getTaskDao(); + taskDao.create(myTask1, new SystemRequestDetails()); + + final List allTasksPreBundle = searchAllTasks(); + assertEquals(1, allTasksPreBundle.size()); + final Task taskPreBundle = allTasksPreBundle.get(0); + assertEquals(VALUE_1, taskPreBundle.getIdentifier().get(0).getValue()); + assertEquals(SYSTEM, taskPreBundle.getIdentifier().get(0).getSystem()); + + final BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); + + final String entryConditionalTemplate = "%sidentifier=http://tempuri.org|1"; + final String matchUrl = String.format(entryConditionalTemplate, theHasQuestionMark ? "?" : ""); + + bundleBuilder.addTransactionCreateEntry(myTask2) + .conditional(matchUrl); + + final List responseEntries = sendBundleAndGetResponse(bundleBuilder.getBundle()); + + assertEquals(1, responseEntries.size()); + + final Bundle.BundleEntryComponent bundleEntry = responseEntries.get(0); + + assertEquals("200 OK", bundleEntry.getResponse().getStatus()); + + final List allTasksPostBundle = searchAllTasks(); + assertEquals(1, allTasksPostBundle.size()); + final Task taskPostBundle = allTasksPostBundle.get(0); + assertEquals(VALUE_1, taskPostBundle.getIdentifier().get(0).getValue()); + assertEquals(SYSTEM, taskPostBundle.getIdentifier().get(0).getSystem()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void testConditionalCreateDependsOnFirstEntryExisting(boolean theHasQuestionMark) { + final BundleBuilder bundleBuilder = new BundleBuilder(myFhirContext); + + bundleBuilder.addTransactionCreateEntry(myTask1, "urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea") + .conditional("identifier=http://tempuri.org|1"); + + final String secondEntryConditionalTemplate = "%sidentifier=http://tempuri.org|2&based-on=urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"; + final String secondMatchUrl = String.format(secondEntryConditionalTemplate, theHasQuestionMark ? "?" : ""); + + bundleBuilder.addTransactionCreateEntry(myTask2) + .conditional(secondMatchUrl); + + final IBaseBundle requestBundle = bundleBuilder.getBundle(); + assertTrue(requestBundle instanceof Bundle); + + final List responseEntries = sendBundleAndGetResponse(requestBundle); + + assertEquals(2, responseEntries.size()); + assertEquals(Set.of("201 Created"), responseEntries.stream().map(Bundle.BundleEntryComponent::getResponse).map(Bundle.BundleEntryResponseComponent::getStatus).collect(Collectors.toUnmodifiableSet())); + + final List allTasksPostBundle = searchAllTasks(); + assertEquals(2, allTasksPostBundle.size()); + final Task taskPostBundle1 = allTasksPostBundle.get(0); + assertEquals(VALUE_1, taskPostBundle1.getIdentifier().get(0).getValue()); + assertEquals(SYSTEM, taskPostBundle1.getIdentifier().get(0).getSystem()); + final Task taskPostBundle2 = allTasksPostBundle.get(1); + assertEquals(VALUE_2, taskPostBundle2.getIdentifier().get(0).getValue()); + assertEquals(SYSTEM, taskPostBundle2.getIdentifier().get(0).getSystem()); + + final List task2BasedOn = taskPostBundle2.getBasedOn(); + assertEquals(1, task2BasedOn.size()); + final Reference task2BasedOnReference = task2BasedOn.get(0); + assertEquals(taskPostBundle1.getIdElement().toUnqualifiedVersionless().asStringValue(), task2BasedOnReference.getReference()); + } + } + + private List sendBundleAndGetResponse(IBaseBundle theRequestBundle) { + assertTrue(theRequestBundle instanceof Bundle); + + return mySystemDao.transaction(new SystemRequestDetails(), (Bundle)theRequestBundle).getEntry(); + } + + private List searchAllTasks() { + return unsafeCast(getTaskDao().search(SearchParameterMap.newSynchronous(), new SystemRequestDetails()).getAllResources()); + } + + private IFhirResourceDao getTaskDao() { + return unsafeCast(myDaoRegistry.getResourceDao("Task")); + } + + @SuppressWarnings("unchecked") + private static T unsafeCast(Object theObject) { + return (T)theObject; + } } diff --git a/hapi-fhir-storage-test-utilities/src/test/java/ca/uhn/fhir/storage/test/BaseTransactionProcessorTest.java b/hapi-fhir-storage-test-utilities/src/test/java/ca/uhn/fhir/storage/test/BaseTransactionProcessorTest.java index f8e70e6b624b..d19088baaf92 100644 --- a/hapi-fhir-storage-test-utilities/src/test/java/ca/uhn/fhir/storage/test/BaseTransactionProcessorTest.java +++ b/hapi-fhir-storage-test-utilities/src/test/java/ca/uhn/fhir/storage/test/BaseTransactionProcessorTest.java @@ -110,4 +110,21 @@ void testUnqualifiedMatchUrlStart_RegexPatternMatches() { assertTrue(matchResult, "Failed to find a Regex match using Url '" + matchUrl + "'"); } + @Test + void identifierSubstitutionNoQuestionMark() { + final IdSubstitutionMap idSubstitutions = new IdSubstitutionMap(); + idSubstitutions.put(new IdType("Task/urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"), new IdType("Task/1/history/1")); + idSubstitutions.put(new IdType("urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"), new IdType("Task/1/_history/1")); + final String outcome = BaseTransactionProcessor.performIdSubstitutionsInMatchUrl(idSubstitutions, "identifier=http://tempuri.org|2&based-on=urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"); + assertEquals("identifier=http://tempuri.org|2&based-on=Task/1", outcome); + } + + @Test + void identifierSubstitutionYesQuestionMar() { + final IdSubstitutionMap idSubstitutions = new IdSubstitutionMap(); + idSubstitutions.put(new IdType("Task/urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"), new IdType("Task/1/history/1")); + idSubstitutions.put(new IdType("urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"), new IdType("Task/1/_history/1")); + final String outcome = BaseTransactionProcessor.performIdSubstitutionsInMatchUrl(idSubstitutions, "?identifier=http://tempuri.org|2&based-on=urn:uuid:59cda086-4763-4ef0-8e36-8c90058686ea"); + assertEquals("?identifier=http://tempuri.org|2&based-on=Task/1", outcome); + } } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java index 05f912d0150b..4c4c153eec85 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -2253,8 +2253,7 @@ private static String toStatusString(int theStatusCode) { public static String performIdSubstitutionsInMatchUrl(IdSubstitutionMap theIdSubstitutions, String theMatchUrl) { String matchUrl = theMatchUrl; if (isNotBlank(matchUrl) && !theIdSubstitutions.isEmpty()) { - - int startIdx = matchUrl.indexOf('?'); + int startIdx = 0; while (startIdx != -1) { int endIdx = matchUrl.indexOf('&', startIdx + 1); From 0537ab58dbc96d5a4155388dadca33c6ce773040 Mon Sep 17 00:00:00 2001 From: Nathan Doef Date: Fri, 2 Feb 2024 11:12:00 -0500 Subject: [PATCH 16/24] Searching for Bundles with read all Bundles permission returns 403 (#5644) * failing test * another failing test case * fix * changelog * fix bug * spotless * cr --- .../fhir/model/valueset/BundleTypeEnum.java | 2 +- .../java/ca/uhn/fhir/util/BundleUtil.java | 9 + ...d-all-bundles-permissions-returns-403.yaml | 8 + .../r4/AuthorizationInterceptorJpaR4Test.java | 260 +++++++++++++++++- .../auth/AuthorizationInterceptor.java | 41 ++- .../server/interceptor/auth/RuleImplOp.java | 2 +- .../uhn/fhir/util/bundle/BundleUtilTest.java | 36 ++- 7 files changed, 346 insertions(+), 12 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5644-searching-for-bundles-with-read-all-bundles-permissions-returns-403.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java index 384356337b04..0710a5533f4f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/valueset/BundleTypeEnum.java @@ -91,7 +91,7 @@ public String getSystem() { /** * Returns the enumerated value associated with this code */ - public BundleTypeEnum forCode(String theCode) { + public static BundleTypeEnum forCode(String theCode) { BundleTypeEnum retVal = CODE_TO_ENUM.get(theCode); return retVal; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java index c31a28071369..3bd503eb5c5f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -235,6 +236,14 @@ public static String getBundleType(FhirContext theContext, IBaseBundle theBundle return null; } + public static BundleTypeEnum getBundleTypeEnum(FhirContext theContext, IBaseBundle theBundle) { + String bundleTypeCode = BundleUtil.getBundleType(theContext, theBundle); + if (isBlank(bundleTypeCode)) { + return null; + } + return BundleTypeEnum.forCode(bundleTypeCode); + } + public static void setBundleType(FhirContext theContext, IBaseBundle theBundle, String theType) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); BaseRuntimeChildDefinition entryChild = def.getChildByName("type"); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5644-searching-for-bundles-with-read-all-bundles-permissions-returns-403.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5644-searching-for-bundles-with-read-all-bundles-permissions-returns-403.yaml new file mode 100644 index 000000000000..ee53104bf804 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5644-searching-for-bundles-with-read-all-bundles-permissions-returns-403.yaml @@ -0,0 +1,8 @@ +--- +type: fix +issue: 5644 +title: "Previously, searching for `Bundle` resources with read all `Bundle` resources permissions, returned an +HTTP 403 Forbidden error. This was because the `AuthorizationInterceptor` applied permissions to the resources inside +the `Bundle`, instead of the `Bundle` itself. This has been fixed and permissions are no longer applied to the resources +inside a `Bundle` of type `document`, `message`, or `collection` for `Bundle` requests." + diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java index 9001bbcd9254..69fa8ebbac2f 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/AuthorizationInterceptorJpaR4Test.java @@ -1,7 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; import ca.uhn.fhir.i18n.Msg; -import ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider; import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc; import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor; import ca.uhn.fhir.jpa.model.util.JpaConstants; @@ -14,6 +13,7 @@ import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; import ca.uhn.fhir.rest.client.interceptor.SimpleRequestHeaderInterceptor; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; @@ -37,11 +37,13 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CodeableConcept; import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Composition; import org.hl7.fhir.r4.model.Condition; import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.ExplanationOfBenefit; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Observation.ObservationStatus; import org.hl7.fhir.r4.model.Organization; @@ -49,11 +51,14 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -68,7 +73,9 @@ import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Test { @@ -79,6 +86,8 @@ public class AuthorizationInterceptorJpaR4Test extends BaseResourceProviderR4Tes private SearchParamMatcher mySearchParamMatcher; @Autowired private ThreadSafeResourceDeleterSvc myThreadSafeResourceDeleterSvc; + private AuthorizationInterceptor myReadAllBundleInterceptor; + private AuthorizationInterceptor myReadAllPatientInterceptor; @BeforeEach @Override @@ -87,7 +96,8 @@ public void before() throws Exception { myStorageSettings.setAllowMultipleDelete(true); myStorageSettings.setExpungeEnabled(true); myStorageSettings.setDeleteExpungeEnabled(true); - myServer.getRestfulServer().registerInterceptor(new BulkDataExportProvider()); + myReadAllBundleInterceptor = new ReadAllAuthorizationInterceptor("Bundle"); + myReadAllPatientInterceptor = new ReadAllAuthorizationInterceptor("Patient"); } @Override @@ -1506,4 +1516,250 @@ public List buildRuleList(RequestDetails theRequestDetails) { } } + + @Test + public void testSearchBundles_withPermissionToSearchAllBundles_doesNotReturn403ForbiddenForDocumentBundles(){ + myServer.getRestfulServer().registerInterceptor(myReadAllBundleInterceptor); + + Bundle bundle1 = createDocumentBundle(createPatient("John", "Smith")); + Bundle bundle2 = createDocumentBundle(createPatient("Jane", "Doe")); + assertSearchContainsResources("/Bundle", bundle1, bundle2); + } + + @Test + public void testSearchBundles_withPermissionToSearchAllBundles_doesNotReturn403ForbiddenForCollectionBundles(){ + myServer.getRestfulServer().registerInterceptor(myReadAllBundleInterceptor); + + Bundle bundle1 = createCollectionBundle(createPatient("John", "Smith")); + Bundle bundle2 = createCollectionBundle(createPatient("Jane", "Doe")); + assertSearchContainsResources("/Bundle", bundle1, bundle2); + } + + @Test + public void testSearchBundles_withPermissionToSearchAllBundles_doesNotReturn403ForbiddenForMessageBundles(){ + myServer.getRestfulServer().registerInterceptor(myReadAllBundleInterceptor); + + Bundle bundle1 = createMessageHeaderBundle(createPatient("John", "Smith")); + Bundle bundle2 = createMessageHeaderBundle(createPatient("Jane", "Doe")); + assertSearchContainsResources("/Bundle", bundle1, bundle2); + } + + @Test + public void testSearchBundles_withPermissionToViewOneBundle_onlyAllowsViewingOneBundle(){ + Bundle bundle1 = createMessageHeaderBundle(createPatient("John", "Smith")); + Bundle bundle2 = createMessageHeaderBundle(createPatient("Jane", "Doe")); + + myServer.getRestfulServer().getInterceptorService().registerInterceptor( + new ReadInCompartmentAuthorizationInterceptor("Bundle", bundle1.getIdElement()) + ); + + assertSearchContainsResources("/Bundle?_id=" + bundle1.getIdPart(), bundle1); + assertSearchFailsWith403Forbidden("/Bundle?_id=" + bundle2.getIdPart()); + assertSearchFailsWith403Forbidden("/Bundle"); + } + + @Test + public void testSearchPatients_withPermissionToSearchAllBundles_returns403Forbidden(){ + myServer.getRestfulServer().registerInterceptor(myReadAllBundleInterceptor); + + createPatient("John", "Smith"); + createPatient("Jane", "Doe"); + assertSearchFailsWith403Forbidden("/Patient"); + } + + @Test + public void testSearchPatients_withPermissionToSearchAllPatients_returnsAllPatients(){ + myServer.getRestfulServer().registerInterceptor(myReadAllPatientInterceptor); + + Patient patient1 = createPatient("John", "Smith"); + Patient patient2 = createPatient("Jane", "Doe"); + assertSearchContainsResources("/Patient", patient1, patient2); + } + + @Test + public void testSearchPatients_withPermissionToViewOnePatient_onlyAllowsViewingOnePatient(){ + Patient patient1 = createPatient("John", "Smith"); + Patient patient2 = createPatient("Jane", "Doe"); + + myServer.getRestfulServer().getInterceptorService().registerInterceptor( + new ReadInCompartmentAuthorizationInterceptor("Patient", patient1.getIdElement()) + ); + + assertSearchContainsResources("/Patient?_id=" + patient1.getIdPart(), patient1); + assertSearchFailsWith403Forbidden("/Patient?_id=" + patient2.getIdPart()); + assertSearchFailsWith403Forbidden("/Patient"); + } + + @Test + public void testToListOfResourcesAndExcludeContainer_withSearchSetContainingDocumentBundles_onlyRecursesOneLevelDeep() { + Bundle bundle1 = createDocumentBundle(createPatient("John", "Smith")); + Bundle bundle2 = createDocumentBundle(createPatient("John", "Smith")); + Bundle searchSet = createSearchSet(bundle1, bundle2); + + RequestDetails requestDetails = new SystemRequestDetails(); + requestDetails.setResourceName("Bundle"); + + List resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(requestDetails, searchSet, myFhirContext); + assertEquals(2, resources.size()); + assertTrue(resources.contains(bundle1)); + assertTrue(resources.contains(bundle2)); + } + + @Test + public void testToListOfResourcesAndExcludeContainer_withSearchSetContainingPatients_returnsPatients() { + Patient patient1 = createPatient("John", "Smith"); + Patient patient2 = createPatient("Jane", "Doe"); + Bundle searchSet = createSearchSet(patient1, patient2); + + RequestDetails requestDetails = new SystemRequestDetails(); + requestDetails.setResourceName("Patient"); + + List resources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer(requestDetails, searchSet, myFhirContext); + assertEquals(2, resources.size()); + assertTrue(resources.contains(patient1)); + assertTrue(resources.contains(patient2)); + } + + @ParameterizedTest + @EnumSource(value = Bundle.BundleType.class, names = {"DOCUMENT", "COLLECTION", "MESSAGE"}) + public void testShouldExamineBundleResources_withBundleRequestAndStandAloneBundleType_returnsFalse(Bundle.BundleType theBundleType){ + RequestDetails requestDetails = new SystemRequestDetails(); + requestDetails.setResourceName("Bundle"); + Bundle bundle = new Bundle(); + bundle.setType(theBundleType); + + assertFalse(AuthorizationInterceptor.shouldExamineBundleChildResources(requestDetails, myFhirContext, bundle)); + } + + @ParameterizedTest + @EnumSource(value = Bundle.BundleType.class, names = {"DOCUMENT", "COLLECTION", "MESSAGE"}, mode= EnumSource.Mode.EXCLUDE) + public void testShouldExamineBundleResources_withBundleRequestAndNonStandAloneBundleType_returnsTrue(Bundle.BundleType theBundleType){ + RequestDetails requestDetails = new SystemRequestDetails(); + requestDetails.setResourceName("Bundle"); + Bundle bundle = new Bundle(); + bundle.setType(theBundleType); + + assertTrue(AuthorizationInterceptor.shouldExamineBundleChildResources(requestDetails, myFhirContext, bundle)); + } + + @ParameterizedTest + @EnumSource(value = Bundle.BundleType.class) + public void testShouldExamineBundleResources_withNonBundleRequests_returnsTrue(Bundle.BundleType theBundleType){ + RequestDetails requestDetails = new SystemRequestDetails(); + requestDetails.setResourceName("Patient"); + Bundle bundle = new Bundle(); + bundle.setType(theBundleType); + + assertTrue(AuthorizationInterceptor.shouldExamineBundleChildResources(requestDetails, myFhirContext, bundle)); + } + + private Patient createPatient(String theFirstName, String theLastName){ + Patient patient = new Patient(); + patient.addName().addGiven(theFirstName).setFamily(theLastName); + return (Patient) myPatientDao.create(patient, mySrd).getResource(); + } + + private Bundle createDocumentBundle(Patient thePatient){ + Composition composition = new Composition(); + composition.setType(new CodeableConcept().addCoding(new Coding().setSystem("http://example.org").setCode("some-type"))); + composition.getSubject().setReference(thePatient.getIdElement().getValue()); + + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.DOCUMENT); + bundle.addEntry().setResource(composition); + bundle.addEntry().setResource(thePatient); + return (Bundle) myBundleDao.create(bundle, mySrd).getResource(); + } + + private Bundle createCollectionBundle(Patient thePatient) { + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.COLLECTION); + bundle.addEntry().setResource(thePatient); + return (Bundle) myBundleDao.create(bundle, mySrd).getResource(); + } + + private Bundle createMessageHeaderBundle(Patient thePatient) { + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.MESSAGE); + + MessageHeader messageHeader = new MessageHeader(); + Coding event = new Coding().setSystem("http://acme.com").setCode("some-event"); + messageHeader.setEvent(event); + messageHeader.getFocusFirstRep().setReference(thePatient.getIdElement().getValue()); + bundle.addEntry().setResource(messageHeader); + bundle.addEntry().setResource(thePatient); + + return (Bundle) myBundleDao.create(bundle, mySrd).getResource(); + } + + private void assertSearchContainsResources(String theUrl, Resource... theExpectedResources){ + List expectedIds = Arrays.stream(theExpectedResources) + .map(resource -> resource.getIdPart()) + .toList(); + + Bundle searchResult = myClient + .search() + .byUrl(theUrl) + .returnBundle(Bundle.class) + .execute(); + + List actualIds = searchResult.getEntry().stream() + .map(entry -> entry.getResource().getIdPart()) + .toList(); + + assertEquals(expectedIds.size(), actualIds.size()); + assertTrue(expectedIds.containsAll(actualIds)); + } + + private void assertSearchFailsWith403Forbidden(String theUrl){ + try { + myClient.search().byUrl(theUrl).execute(); + fail(); + } catch (Exception e){ + assertTrue(e.getMessage().contains("HTTP 403 Forbidden")); + } + } + + private Bundle createSearchSet(Resource... theResources){ + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.SEARCHSET); + Arrays.stream(theResources).forEach(resource -> bundle.addEntry().setResource(resource)); + return bundle; + } + + static class ReadAllAuthorizationInterceptor extends AuthorizationInterceptor { + + private final String myResourceType; + + public ReadAllAuthorizationInterceptor(String theResourceType){ + super(PolicyEnum.DENY); + myResourceType = theResourceType; + } + + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().read().resourcesOfType(myResourceType).withAnyId().andThen() + .build(); + } + } + + static class ReadInCompartmentAuthorizationInterceptor extends AuthorizationInterceptor { + + private final String myResourceType; + private final IIdType myId; + + public ReadInCompartmentAuthorizationInterceptor(String theResourceType, IIdType theId){ + super(PolicyEnum.DENY); + myResourceType = theResourceType; + myId = theId; + } + + @Override + public List buildRuleList(RequestDetails theRequestDetails) { + return new RuleBuilder() + .allow().read().allResources().inCompartment(myResourceType, myId).andThen() + .build(); + } + } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java index 534d9cd9dde0..1efa0e7e7513 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/AuthorizationInterceptor.java @@ -25,12 +25,14 @@ import ca.uhn.fhir.interceptor.api.Hook; import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters; import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; import ca.uhn.fhir.rest.server.interceptor.consent.ConsentInterceptor; +import ca.uhn.fhir.util.BundleUtil; import com.google.common.collect.Lists; import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; @@ -78,8 +80,12 @@ public class AuthorizationInterceptor implements IRuleApplier { public static final String REQUEST_ATTRIBUTE_BULK_DATA_EXPORT_OPTIONS = AuthorizationInterceptor.class.getName() + "_BulkDataExportOptions"; + public static final String BUNDLE = "Bundle"; private static final AtomicInteger ourInstanceCount = new AtomicInteger(0); private static final Logger ourLog = LoggerFactory.getLogger(AuthorizationInterceptor.class); + private static final Set STANDALONE_BUNDLE_RESOURCE_TYPES = + Set.of(BundleTypeEnum.DOCUMENT, BundleTypeEnum.COLLECTION, BundleTypeEnum.MESSAGE); + private final int myInstanceIndex = ourInstanceCount.incrementAndGet(); private final String myRequestSeenResourcesKey = AuthorizationInterceptor.class.getName() + "_" + myInstanceIndex + "_SEENRESOURCES"; @@ -525,7 +531,7 @@ private void checkOutgoingResourceAndFailIfDeny( case EXTENDED_OPERATION_TYPE: case EXTENDED_OPERATION_INSTANCE: { if (theResponseObject != null) { - resources = toListOfResourcesAndExcludeContainer(theResponseObject, fhirContext); + resources = toListOfResourcesAndExcludeContainer(theRequestDetails, theResponseObject, fhirContext); } break; } @@ -572,22 +578,23 @@ private enum OperationExamineDirection { OUT, } - static List toListOfResourcesAndExcludeContainer( - IBaseResource theResponseObject, FhirContext fhirContext) { + public static List toListOfResourcesAndExcludeContainer( + RequestDetails theRequestDetails, IBaseResource theResponseObject, FhirContext fhirContext) { if (theResponseObject == null) { return Collections.emptyList(); } List retVal; - boolean isContainer = false; + boolean shouldExamineChildResources = false; if (theResponseObject instanceof IBaseBundle) { - isContainer = true; + IBaseBundle bundle = (IBaseBundle) theResponseObject; + shouldExamineChildResources = shouldExamineBundleChildResources(theRequestDetails, fhirContext, bundle); } else if (theResponseObject instanceof IBaseParameters) { - isContainer = true; + shouldExamineChildResources = true; } - if (!isContainer) { + if (!shouldExamineChildResources) { return Collections.singletonList(theResponseObject); } @@ -604,6 +611,26 @@ static List toListOfResourcesAndExcludeContainer( return retVal; } + /** + * This method determines if the given Bundle should have permissions applied to the resources inside or + * to the Bundle itself. + * + * This distinction is important in Bundle requests where a user has permissions to view all Bundles. In + * this scenario we want to apply permissions to the Bundle itself and not the resources inside if + * the Bundle is of type document, collection, or message. + */ + public static boolean shouldExamineBundleChildResources( + RequestDetails theRequestDetails, FhirContext theFhirContext, IBaseBundle theBundle) { + boolean isBundleRequest = theRequestDetails != null && BUNDLE.equals(theRequestDetails.getResourceName()); + if (!isBundleRequest) { + return true; + } + BundleTypeEnum bundleType = BundleUtil.getBundleTypeEnum(theFhirContext, theBundle); + boolean isStandaloneBundleResource = + bundleType != null && STANDALONE_BUNDLE_RESOURCE_TYPES.contains(bundleType); + return !isStandaloneBundleResource; + } + public static class Verdict { private final IAuthRule myDecidingRule; diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java index 325ebd6e8496..f5c2dd1b1410 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/auth/RuleImplOp.java @@ -817,7 +817,7 @@ private Verdict applyRuleToTransaction( } else if (theOutputResource != null) { List outputResources = AuthorizationInterceptor.toListOfResourcesAndExcludeContainer( - theOutputResource, theRequestDetails.getFhirContext()); + theRequestDetails, theOutputResource, theRequestDetails.getFhirContext()); Verdict verdict = null; for (IBaseResource nextResource : outputResources) { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java index ea057c976232..20059362e678 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/bundle/BundleUtilTest.java @@ -3,10 +3,12 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; +import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.TestUtil; +import jakarta.annotation.Nonnull; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -25,8 +27,9 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; -import jakarta.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.function.Consumer; @@ -555,6 +558,37 @@ public void testGetResourceByReferenceAndResourceTypeReturnsNullIfResourceNotFou assertNull(actual); } + @ParameterizedTest + @CsvSource({ + // Actual BundleType Expected BundleTypeEnum + "TRANSACTION, TRANSACTION", + "DOCUMENT, DOCUMENT", + "MESSAGE, MESSAGE", + "BATCHRESPONSE, BATCH_RESPONSE", + "TRANSACTIONRESPONSE, TRANSACTION_RESPONSE", + "HISTORY, HISTORY", + "SEARCHSET, SEARCHSET", + "COLLECTION, COLLECTION" + }) + public void testGetBundleTypeEnum_withKnownBundleTypes_returnsCorrectBundleTypeEnum(Bundle.BundleType theBundleType, BundleTypeEnum theExpectedBundleTypeEnum){ + Bundle bundle = new Bundle(); + bundle.setType(theBundleType); + assertEquals(theExpectedBundleTypeEnum, BundleUtil.getBundleTypeEnum(ourCtx, bundle)); + } + + @Test + public void testGetBundleTypeEnum_withNullBundleType_returnsNull(){ + Bundle bundle = new Bundle(); + bundle.setType(Bundle.BundleType.NULL); + assertNull(BundleUtil.getBundleTypeEnum(ourCtx, bundle)); + } + + @Test + public void testGetBundleTypeEnum_withNoBundleType_returnsNull(){ + Bundle bundle = new Bundle(); + assertNull(BundleUtil.getBundleTypeEnum(ourCtx, bundle)); + } + @Nonnull private static Bundle withBundle(Resource theResource) { final Bundle bundle = new Bundle(); From 0f3c744f1171b8a2a99d2ddbbcc05e4485f99085 Mon Sep 17 00:00:00 2001 From: Martha Mitran Date: Fri, 2 Feb 2024 10:19:11 -0800 Subject: [PATCH 17/24] Fix NullPointerException when performing a system bulk export in the presence of PatientIdPartitionInterceptor. (#5660) --- ...tem-bulk-export-patientid-interceptor.yaml | 5 ++ .../model/ReadPartitionIdRequestDetails.java | 2 +- .../PatientIdPartitionInterceptorTest.java | 50 ++++++++++++++++--- .../jobs/export/BulkDataExportProvider.java | 6 ++- .../PatientIdPartitionInterceptor.java | 43 ++++++++-------- 5 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5659-npe-system-bulk-export-patientid-interceptor.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5659-npe-system-bulk-export-patientid-interceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5659-npe-system-bulk-export-patientid-interceptor.yaml new file mode 100644 index 000000000000..ea41710dc9c2 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5659-npe-system-bulk-export-patientid-interceptor.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 5659 +title: "Previously, after registering built-in interceptor `PatientIdPartitionInterceptor`, the system bulk export +(with no filters) operation would fail with a NullPointerException. This has been fixed." diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java index 4d3e61d9fce7..4023248362a6 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/interceptor/model/ReadPartitionIdRequestDetails.java @@ -116,7 +116,7 @@ public static ReadPartitionIdRequestDetails forOperation( } else if (theResourceType != null) { op = RestOperationTypeEnum.EXTENDED_OPERATION_TYPE; } else { - op = RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE; + op = RestOperationTypeEnum.EXTENDED_OPERATION_SERVER; } return new ReadPartitionIdRequestDetails(theResourceType, op, null, null, null, null, theExtendedOperationName); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java index 0e432bbcee56..2a15939ab666 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptorTest.java @@ -1,24 +1,32 @@ package ca.uhn.fhir.jpa.interceptor; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; +import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; -import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4SystemTest; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.model.api.Include; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.SystemRequestDetails; +import ca.uhn.fhir.rest.api.server.bulk.BulkExportJobParameters; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.MultimapCollector; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Enumerations; @@ -32,6 +40,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; import java.io.IOException; import java.util.List; @@ -47,21 +56,25 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; -public class PatientIdPartitionInterceptorTest extends BaseJpaR4SystemTest { - +public class PatientIdPartitionInterceptorTest extends BaseResourceProviderR4Test { public static final int ALTERNATE_DEFAULT_ID = -1; - private PatientIdPartitionInterceptor mySvc; + private ForceOffsetSearchModeInterceptor myForceOffsetSearchModeInterceptor; @Autowired private ISearchParamExtractor mySearchParamExtractor; + @SpyBean + @Autowired + private PatientIdPartitionInterceptor mySvc; + @Override @BeforeEach public void before() throws Exception { super.before(); - mySvc = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings); myForceOffsetSearchModeInterceptor = new ForceOffsetSearchModeInterceptor(); myInterceptorRegistry.registerInterceptor(mySvc); @@ -539,4 +552,29 @@ private Patient createPatientA() { return (Patient)update.getResource(); } + @Test + public void testIdentifyForRead_serverOperation_returnsAllPartitions() { + ReadPartitionIdRequestDetails readRequestDetails = ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPORT); + RequestPartitionId requestPartitionId = mySvc.identifyForRead(readRequestDetails, mySrd); + assertEquals(requestPartitionId, RequestPartitionId.allPartitions()); + assertEquals(RestOperationTypeEnum.EXTENDED_OPERATION_SERVER, readRequestDetails.getRestOperationType()); + } + + @Test + public void testSystemBulkExport_withPatientIdPartitioningWithNoResourceType_usesNonPatientSpecificPartition() throws IOException { + final BulkExportJobParameters options = new BulkExportJobParameters(); + options.setExportStyle(BulkExportJobParameters.ExportStyle.SYSTEM); + options.setOutputFormat(Constants.CT_FHIR_NDJSON); + + HttpPost post = new HttpPost(myServer.getBaseUrl() + "/" + ProviderConstants.OPERATION_EXPORT); + post.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RESPOND_ASYNC); + + try (CloseableHttpResponse postResponse = myServer.getHttpClient().execute(post)){ + ourLog.info("Response: {}",postResponse); + assertEquals(202, postResponse.getStatusLine().getStatusCode()); + assertEquals("Accepted", postResponse.getStatusLine().getReasonPhrase()); + } + + verify(mySvc).provideNonPatientSpecificQueryResponse(any()); + } } diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java index 1df5e286a99c..b08e4bcba177 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/BulkDataExportProvider.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.interceptor.model.ReadPartitionIdRequestDetails; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.api.config.JpaStorageSettings; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; @@ -185,9 +186,12 @@ private void startJob(ServletRequestDetails theRequestDetails, BulkExportJobPara theOptions.setResourceTypes(resourceTypes); } + ReadPartitionIdRequestDetails theDetails = + ReadPartitionIdRequestDetails.forOperation(null, null, ProviderConstants.OPERATION_EXPORT); + // Determine and validate partition permissions (if needed). RequestPartitionId partitionId = - myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, null); + myRequestPartitionHelperService.determineReadPartitionForRequest(theRequestDetails, theDetails); myRequestPartitionHelperService.validateHasPartitionPermissions(theRequestDetails, "Binary", partitionId); theOptions.setPartitionId(partitionId); diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java index f399b19a383e..a1399ddb5927 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/interceptor/PatientIdPartitionInterceptor.java @@ -33,21 +33,21 @@ import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; import ca.uhn.fhir.jpa.util.ResourceCompartmentUtil; import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; +import ca.uhn.fhir.rest.server.provider.ProviderConstants; import jakarta.annotation.Nonnull; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.IdType; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.commons.lang3.StringUtils.isNotBlank; /** @@ -117,14 +117,15 @@ public RequestPartitionId identifyForCreate(IBaseResource theResource, RequestDe @Hook(Pointcut.STORAGE_PARTITION_IDENTIFY_READ) public RequestPartitionId identifyForRead( ReadPartitionIdRequestDetails theReadDetails, RequestDetails theRequestDetails) { - if (isBlank(theReadDetails.getResourceType())) { - return provideNonCompartmentMemberTypeResponse(null); - } - RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theReadDetails.getResourceType()); - List compartmentSps = - ResourceCompartmentUtil.getPatientCompartmentSearchParams(resourceDef); - if (compartmentSps.isEmpty()) { - return provideNonCompartmentMemberTypeResponse(null); + + List compartmentSps = Collections.emptyList(); + if (!isEmpty(theReadDetails.getResourceType())) { + RuntimeResourceDefinition resourceDef = + myFhirContext.getResourceDefinition(theReadDetails.getResourceType()); + compartmentSps = ResourceCompartmentUtil.getPatientCompartmentSearchParams(resourceDef); + if (compartmentSps.isEmpty()) { + return provideNonCompartmentMemberTypeResponse(null); + } } //noinspection EnumSwitchStatementWhichMissesCases @@ -158,11 +159,20 @@ public RequestPartitionId identifyForRead( } break; - + case EXTENDED_OPERATION_SERVER: + String extendedOp = theReadDetails.getExtendedOperationName(); + if (ProviderConstants.OPERATION_EXPORT.equals(extendedOp)) { + return provideNonPatientSpecificQueryResponse(theReadDetails); + } + break; default: // nothing } + if (isEmpty(theReadDetails.getResourceType())) { + return provideNonCompartmentMemberTypeResponse(null); + } + // If we couldn't identify a patient ID by the URL, let's try using the // conditional target if we have one if (theReadDetails.getConditionalTargetOrNull() != null) { @@ -172,15 +182,6 @@ public RequestPartitionId identifyForRead( return provideNonPatientSpecificQueryResponse(theReadDetails); } - @Nonnull - private List getCompartmentSearchParams(RuntimeResourceDefinition resourceDef) { - return resourceDef.getSearchParams().stream() - .filter(param -> param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) - .filter(param -> param.getProvidesMembershipInCompartments() != null - && param.getProvidesMembershipInCompartments().contains("Patient")) - .collect(Collectors.toList()); - } - private List getResourceIdList( SearchParameterMap theParams, String theParamName, String theResourceType, boolean theExpectOnlyOneBool) { List idParts = new ArrayList<>(); From 14a489c2afd70efc796a1eb55a3365985acd6374 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Fri, 2 Feb 2024 17:38:15 -0800 Subject: [PATCH 18/24] Serializing changes for sensitive data (#5655) * Add new senstiive data serializer * Add new senstiive data serializer * Add new senstiive data serializer * Remove dead comments * Change up the test * review comments * wip * Tighten tests and push annotation down * Tighten tests and push annotation down * Changelog * Add test * 7.0.1-SNAPSHOT bump * Error code * Add method used by CDR * add version enum * Fix test --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../ca/uhn/fhir/model/api/IModelJson.java | 4 +- .../api/annotation/SensitiveNoDisplay.java | 34 ++++++++++ .../main/java/ca/uhn/fhir/util/JsonUtil.java | 67 +++++++++++++++++++ .../java/ca/uhn/fhir/util/VersionEnum.java | 1 + .../java/ca/uhn/fhir/util/JsonUtilTest.java | 54 +++++++++++++++ hapi-fhir-bom/pom.xml | 4 +- hapi-fhir-checkstyle/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-api/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- hapi-fhir-converter/pom.xml | 2 +- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- .../7_0_0/5656-bulk-import-credentials.yaml | 5 ++ hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-jpaserver-hfql/pom.xml | 2 +- hapi-fhir-jpaserver-ips/pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- .../jpa/bulk/imprt2/BulkImportR4Test.java | 54 ++++++++++++++- .../provider/r4/BulkExportProviderR4Test.java | 2 - hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-cds-hooks/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../hapi-fhir-caching-api/pom.xml | 2 +- .../hapi-fhir-caching-caffeine/pom.xml | 4 +- .../hapi-fhir-caching-guava/pom.xml | 2 +- .../hapi-fhir-caching-testing/pom.xml | 2 +- hapi-fhir-serviceloaders/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../hapi-fhir-spring-boot-samples/pom.xml | 2 +- .../hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-sql-migrate/pom.xml | 2 +- hapi-fhir-storage-batch2-jobs/pom.xml | 2 +- .../jobs/imprt/BulkImportFileServlet.java | 29 ++++++++ .../jobs/imprt/BulkImportJobParameters.java | 7 +- .../imprt/BulkDataImportProviderTest.java | 2 +- .../imprt/ParameterSerializationTest.java | 24 +++++++ .../pom.xml | 2 +- hapi-fhir-storage-batch2/pom.xml | 2 +- .../batch2/model/JobInstanceStartRequest.java | 2 +- hapi-fhir-storage-cr/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 2 +- hapi-fhir-structures-r5/pom.xml | 2 +- hapi-fhir-test-utilities/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation-resources-r4b/pom.xml | 2 +- hapi-fhir-validation-resources-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 90 files changed, 356 insertions(+), 87 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SensitiveNoDisplay.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/util/JsonUtilTest.java create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5656-bulk-import-credentials.yaml create mode 100644 hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/ParameterSerializationTest.java diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 23def8db8d42..91cb97fd309a 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 74b94af7cc2d..35200bc1be3f 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index c5fdfd45ac87..0f01d1e498bb 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java index 0157b5216fdb..3bdc16a5f859 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IModelJson.java @@ -29,4 +29,6 @@ getterVisibility = JsonAutoDetect.Visibility.NONE, isGetterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE) -public interface IModelJson {} +public interface IModelJson { + String SENSITIVE_DATA_FILTER_NAME = "sensitiveDataFilter"; +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SensitiveNoDisplay.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SensitiveNoDisplay.java new file mode 100644 index 000000000000..589786239260 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/SensitiveNoDisplay.java @@ -0,0 +1,34 @@ +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2024 Smile CDR, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ +package ca.uhn.fhir.model.api.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark a field as sensitive, indicating that it should not + * be displayed or serialized by jackson. The only way to serialize an object annotated with this annotation is to use + * {@link ca.uhn.fhir.util.JsonUtil}, as it has a registered filter against this annotation. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SensitiveNoDisplay {} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java index 41313311c338..9a56f93a1e48 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/JsonUtil.java @@ -21,15 +21,23 @@ import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.model.api.IModelJson; +import ca.uhn.fhir.model.api.annotation.SensitiveNoDisplay; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.FilterProvider; +import com.fasterxml.jackson.databind.ser.PropertyWriter; +import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; +import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import jakarta.annotation.Nonnull; import java.io.IOException; +import java.io.InputStream; import java.io.StringWriter; import java.io.Writer; import java.util.List; @@ -38,15 +46,30 @@ public class JsonUtil { private static final ObjectMapper ourMapperPrettyPrint; private static final ObjectMapper ourMapperNonPrettyPrint; + private static final ObjectMapper ourMapperIncludeSensitive; + + public static final SimpleBeanPropertyFilter SIMPLE_BEAN_PROPERTY_FILTER = new SensitiveDataFilter(); + + public static final SimpleFilterProvider SENSITIVE_DATA_FILTER_PROVIDER = + new SimpleFilterProvider().addFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME, SIMPLE_BEAN_PROPERTY_FILTER); + public static final SimpleFilterProvider SHOW_ALL_DATA_FILTER_PROVIDER = new SimpleFilterProvider() + .addFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME, SimpleBeanPropertyFilter.serializeAll()); static { ourMapperPrettyPrint = new ObjectMapper(); ourMapperPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL); + ourMapperPrettyPrint.setFilterProvider(SENSITIVE_DATA_FILTER_PROVIDER); ourMapperPrettyPrint.enable(SerializationFeature.INDENT_OUTPUT); ourMapperNonPrettyPrint = new ObjectMapper(); ourMapperNonPrettyPrint.setSerializationInclusion(JsonInclude.Include.NON_NULL); + ourMapperNonPrettyPrint.setFilterProvider(SENSITIVE_DATA_FILTER_PROVIDER); ourMapperNonPrettyPrint.disable(SerializationFeature.INDENT_OUTPUT); + + ourMapperIncludeSensitive = new ObjectMapper(); + ourMapperIncludeSensitive.setFilterProvider(SHOW_ALL_DATA_FILTER_PROVIDER); + ourMapperIncludeSensitive.setSerializationInclusion(JsonInclude.Include.NON_NULL); + ourMapperIncludeSensitive.disable(SerializationFeature.INDENT_OUTPUT); } /** @@ -67,6 +90,24 @@ public static T deserialize(@Nonnull String theInput, @Nonnull Class theT public static List deserializeList(@Nonnull String theInput, @Nonnull Class theType) throws IOException { return ourMapperPrettyPrint.readerForListOf(theType).readValue(theInput); } + /** + * Parse JSON + */ + public static T deserialize(@Nonnull InputStream theInput, @Nonnull Class theType) throws IOException { + return ourMapperPrettyPrint.readerFor(theType).readValue(theInput); + } + + /** + * Includes fields which are annotated with {@link SensitiveNoDisplay}. Currently only meant to be used for serialization + * for batch job parameters. + */ + public static String serializeWithSensitiveData(@Nonnull IModelJson theInput) { + try { + return ourMapperIncludeSensitive.writeValueAsString(theInput); + } catch (JsonProcessingException e) { + throw new InvalidRequestException(Msg.code(2487) + "Failed to encode " + theInput.getClass(), e); + } + } /** * Encode JSON @@ -93,6 +134,10 @@ public static String serialize(@Nonnull Object theInput, boolean thePrettyPrint) } } + public FilterProvider getSensitiveDataFilterProvider() { + return SENSITIVE_DATA_FILTER_PROVIDER; + } + /** * Encode JSON */ @@ -111,4 +156,26 @@ public static String serializeOrInvalidRequest(IModelJson theJson) { throw new InvalidRequestException(Msg.code(1741) + "Failed to encode " + theJson.getClass(), e); } } + + private static class SensitiveDataFilter extends SimpleBeanPropertyFilter { + + @Override + protected boolean include(PropertyWriter writer) { + return true; // Default include all except explicitly checked and excluded + } + + @Override + public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider, PropertyWriter writer) + throws Exception { + if (include(writer)) { + if (!isFieldSensitive(writer)) { + super.serializeAsField(pojo, gen, provider, writer); + } + } + } + + private boolean isFieldSensitive(PropertyWriter writer) { + return writer.getAnnotation(SensitiveNoDisplay.class) != null; + } + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java index abb861c91c05..d59f373bee37 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/VersionEnum.java @@ -135,6 +135,7 @@ public enum VersionEnum { V6_11_0, V7_0_0, + V7_0_1, V7_1_0, V7_2_0; diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/JsonUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/JsonUtilTest.java new file mode 100644 index 000000000000..7d0adfaeea15 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/JsonUtilTest.java @@ -0,0 +1,54 @@ +package ca.uhn.fhir.util; + +import ca.uhn.fhir.model.api.IModelJson; +import ca.uhn.fhir.model.api.annotation.SensitiveNoDisplay; +import com.fasterxml.jackson.annotation.JsonFilter; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +class JsonUtilTest { + + @JsonFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME) + class TestObject implements IModelJson { + @JsonProperty("sensitiveField") + @SensitiveNoDisplay + private String mySensitiveField; + + @JsonProperty(value = "publicField") + private String myPublicField; + + public String getPrivateField() { + return mySensitiveField; + } + + public void setSensitiveField(String thePrivateField) { + this.mySensitiveField = thePrivateField; + } + + public String getPublicField() { + return myPublicField; + } + + public void setPublicField(String thePublicField) { + this.myPublicField = thePublicField; + } + } + + @Test + public void testSensitiveNoDisplayAnnotationIsHiddenFromBasicSerialization() { + TestObject object = new TestObject(); + object.setPublicField("Public Value!"); + object.setSensitiveField("Sensitive Value!"); + + String sensitiveExcluded = JsonUtil.serializeOrInvalidRequest(object); + assertThat(sensitiveExcluded, is(not(containsString("Sensitive Value!")))); + + String sensitiveIncluded = JsonUtil.serializeWithSensitiveData(object); + assertThat(sensitiveIncluded, is(containsString("Sensitive Value!"))); + } +} diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 8e9ef9275d7d..91075b81ba60 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 5edd95c36b73..d5ee16ac59e8 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index 70c272893786..8b15f3a620d1 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index 331c7d180e3b..358ca26222f6 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 03500e4520fa..42e1decc8ed6 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 14529d678290..6e005ebbb7c5 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 0a512023af91..16e103a50e7d 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 98ef7fa89925..1b7b23f8ae30 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 38fac62b89fe..c08597847bc9 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 909784c72fb0..d87246d53dce 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5656-bulk-import-credentials.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5656-bulk-import-credentials.yaml new file mode 100644 index 000000000000..866f8c7bc5ad --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5656-bulk-import-credentials.yaml @@ -0,0 +1,5 @@ +--- +type: fix +jira: SMILE-7216 +title: "Previously, the Bulk Import (`$import`) job was ignoring the `httpBasicCredentials` section of the incoming parameters +object, causing the job to fail with a 403 error. This has been corrected." diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 8559dc4129e9..242b8c1b48de 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 1675f275910e..38e0b03c5fbc 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index 9fa97dcb3f89..b8ab7443f776 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 84f549d2412a..77ec9aefaf15 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 29047652bab9..bd3498cf0da5 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index 5e10c3a8abb7..2fe4f9c66ee9 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index f5581fc6190f..ba62fa8496b1 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index d0101f627047..204c2a328e67 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 6fd6ea4f5920..4abb7b07dc87 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index f2bcfaec463c..8b8df8e00a19 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 9c324951d12a..cfb249e7b37c 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index 13b2838847ff..2933103d52fc 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index e7f3bbf68abb..720ac1e4e0a2 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 05afff797cdf..26b6ba73eacb 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt2/BulkImportR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt2/BulkImportR4Test.java index 62cf902d5703..d8d30c4a61e7 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt2/BulkImportR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/bulk/imprt2/BulkImportR4Test.java @@ -33,11 +33,13 @@ import org.springframework.data.domain.Pageable; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.blankOrNullString; import static org.hamcrest.Matchers.containsString; @@ -52,7 +54,9 @@ public class BulkImportR4Test extends BaseJpaR4Test { private static final Logger ourLog = LoggerFactory.getLogger(BulkImportR4Test.class); - private final BulkImportFileServlet myBulkImportFileServlet = new BulkImportFileServlet(); + private static final String USERNAME = "username"; + private static final String PASSWORD = "password"; + private final BulkImportFileServlet myBulkImportFileServlet = new BulkImportFileServlet(USERNAME, PASSWORD); @RegisterExtension private final HttpServletExtension myHttpServletExtension = new HttpServletExtension() .withServlet(myBulkImportFileServlet); @@ -76,6 +80,45 @@ public void afterEach() { await().until(() -> channel.getQueueSizeForUnitTest() == 0); } + + @Test + public void testBulkImportFailsWith403OnBadCredentials() { + + BulkImportJobParameters parameters = new BulkImportJobParameters(); + String url = myHttpServletExtension.getBaseUrl() + "/download?index=test"; // Name doesnt matter, its going to fail with 403 anyhow + parameters.addNdJsonUrl(url); + JobInstanceStartRequest request = new JobInstanceStartRequest(); + request.setJobDefinitionId(BulkImportAppCtx.JOB_BULK_IMPORT_PULL); + request.setParameters(parameters); + + // Execute + Batch2JobStartResponse startResponse = myJobCoordinator.startInstance(request); + String instanceId = startResponse.getInstanceId(); + assertThat(instanceId, not(blankOrNullString())); + ourLog.info("Execution got ID: {}", instanceId); + + // Verify + await().atMost(120, TimeUnit.SECONDS).until(() -> { + myJobCleanerService.runMaintenancePass(); + JobInstance instance = myJobCoordinator.getInstance(instanceId); + return instance.getStatus(); + }, equalTo(StatusEnum.FAILED)); + + //No resources stored + runInTransaction(() -> { + assertEquals(0, myResourceTableDao.count()); + }); + + + //Should have 403 + runInTransaction(() -> { + JobInstance instance = myJobCoordinator.getInstance(instanceId); + ourLog.info("Instance details:\n{}", JsonUtil.serialize(instance, true)); + assertEquals(1, instance.getErrorCount()); + assertThat(instance.getErrorMessage(), is(containsString("Received HTTP 403"))); + }); + + } @Test public void testRunBulkImport() { // Setup @@ -84,6 +127,8 @@ public void testRunBulkImport() { List indexes = addFiles(fileCount); BulkImportJobParameters parameters = new BulkImportJobParameters(); + + parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD); for (String next : indexes) { String url = myHttpServletExtension.getBaseUrl() + "/download?index=" + next; parameters.addNdJsonUrl(url); @@ -132,6 +177,7 @@ public void testRunBulkImport_StorageFailure() { List indexes = addFiles(fileCount); BulkImportJobParameters parameters = new BulkImportJobParameters(); + parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD); for (String next : indexes) { String url = myHttpServletExtension.getBaseUrl() + "/download?index=" + next; parameters.addNdJsonUrl(url); @@ -219,6 +265,7 @@ public void testRunBulkImport_InvalidFileContents() { indexes.add(myBulkImportFileServlet.registerFileByContents("{\"resourceType\":\"Foo\"}")); BulkImportJobParameters parameters = new BulkImportJobParameters(); + parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD); for (String next : indexes) { String url = myHttpServletExtension.getBaseUrl() + "/download?index=" + next; parameters.addNdJsonUrl(url); @@ -260,6 +307,7 @@ public void testRunBulkImport_UnknownTargetFile() { BulkImportJobParameters parameters = new BulkImportJobParameters(); String url = myHttpServletExtension.getBaseUrl() + "/download?index=FOO"; parameters.addNdJsonUrl(url); + parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD); JobInstanceStartRequest request = new JobInstanceStartRequest(); request.setJobDefinitionId(BulkImportAppCtx.JOB_BULK_IMPORT_PULL); @@ -328,7 +376,9 @@ public void testStartInvalidJob_NoUrls() { JobInstanceStartRequest request = new JobInstanceStartRequest(); request.setJobDefinitionId(BulkImportAppCtx.JOB_BULK_IMPORT_PULL); - request.setParameters(new BulkImportJobParameters()); + BulkImportJobParameters parameters = new BulkImportJobParameters(); + parameters.setHttpBasicCredentials(USERNAME + ":" + PASSWORD); + request.setParameters(parameters); // Execute diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/BulkExportProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/BulkExportProviderR4Test.java index d0a1c576f1c3..dd1a0d99f5fa 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/BulkExportProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/BulkExportProviderR4Test.java @@ -43,8 +43,6 @@ void testBulkExport_patientNotExists_throws404() { assertThat(e.getStatusCode(), equalTo(404)); } - - @Test void testBulkExport_typePatientIdNotExists_throws404() { // given no data diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index f00ff0251f14..39ff58378cf7 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 0aac452bcd16..785e63326138 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index a81d904eba5c..f87a6a546d6d 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 01b61f2c55ae..36bf758da6af 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index eb0d99602936..ccd4603f61b8 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 285f0c5e358e..a74ece47de12 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 8bc4912e8b51..ac52a922e74f 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index a4f9b7f42617..69987ccdae33 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index 43551b1c2dd1..7bdc1a4e69b5 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index de1719b06ee7..8097de3dff02 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index d9365b831482..e49df541167f 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index 69da3ffa5466..a222b9d8246b 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index fa98b63e3b02..fc8c0016dfe0 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 29d7cee14c89..c8a005626ee2 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index 76e89cd70516..234b77966971 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index 2abfa034942b..26d069ea69e3 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index 23d33465acea..059dfad3ec39 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 9090342c16ee..a2a17aa72481 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index 638fb2de9f35..8881afc4e87d 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 059bc9dad25b..b4eec66f0ba4 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 26ee701074ef..632861159409 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index c3f40aacd575..79c12b955d44 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportFileServlet.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportFileServlet.java index 0943e5b2d311..c2ab1cfee058 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportFileServlet.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportFileServlet.java @@ -38,6 +38,7 @@ import java.io.Reader; import java.io.StringReader; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -56,8 +57,36 @@ public class BulkImportFileServlet extends HttpServlet { public static final String DEFAULT_HEADER_CONTENT_TYPE = CT_FHIR_NDJSON + CHARSET_UTF8_CTSUFFIX; + private String myBasicAuth; + + public BulkImportFileServlet() {} + + public BulkImportFileServlet(String theBasicAuthUsername, String theBasicAuthPassword) { + setBasicAuth(theBasicAuthUsername, theBasicAuthPassword); + } + + public void setBasicAuth(String username, String password) { + String auth = username + ":" + password; + String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes()); + myBasicAuth = "Basic " + encodedAuth; + } + + public void checkBasicAuthAndMaybeThrow403(HttpServletRequest request, HttpServletResponse response) + throws IOException { + // Check if the myBasicAuth variable is set, ignore if not. + if (myBasicAuth == null || myBasicAuth.isEmpty()) { + return; + } + + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.equals(myBasicAuth)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid authentication credentials."); + } + } + @Override protected void doGet(HttpServletRequest theRequest, HttpServletResponse theResponse) throws IOException { + checkBasicAuthAndMaybeThrow403(theRequest, theResponse); try { String servletPath = theRequest.getServletPath(); String requestUri = theRequest.getRequestURI(); diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportJobParameters.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportJobParameters.java index 5ede8fd04385..9617db73a0f3 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportJobParameters.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/imprt/BulkImportJobParameters.java @@ -21,6 +21,8 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.model.api.IModelJson; +import ca.uhn.fhir.model.api.annotation.SensitiveNoDisplay; +import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.annotation.Nullable; import jakarta.validation.constraints.Min; @@ -36,6 +38,8 @@ * This class is the parameters model object for starting a * bulk import job. */ +@JsonFilter(IModelJson.SENSITIVE_DATA_FILTER_NAME) // TODO GGG eventually consider pushing this up once we have more +// experience using it. public class BulkImportJobParameters implements IModelJson { @JsonProperty(value = "ndJsonUrls", required = true) @@ -43,8 +47,9 @@ public class BulkImportJobParameters implements IModelJson { @NotNull(message = "At least one NDJSON URL must be provided") private List<@Pattern(regexp = "^http[s]?://.*", message = "Must be a valid URL") String> myNdJsonUrls; - @JsonProperty(value = "httpBasicCredentials", access = JsonProperty.Access.WRITE_ONLY, required = false) + @JsonProperty(value = "httpBasicCredentials", required = false) @Nullable + @SensitiveNoDisplay private String myHttpBasicCredentials; @JsonProperty(value = "maxBatchResourceCount", required = false) diff --git a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProviderTest.java b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProviderTest.java index 4d79239db445..1445fc7bb837 100644 --- a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProviderTest.java +++ b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/BulkDataImportProviderTest.java @@ -154,7 +154,7 @@ public void testStartWithPartitioning_Success(Class type, boolean partitionEn JobInstanceStartRequest startRequest = myStartRequestCaptor.getValue(); ourLog.info("Parameters: {}", startRequest.getParameters()); - assertTrue(startRequest.getParameters().startsWith("{\"ndJsonUrls\":[\"http://example.com/Patient\",\"http://example.com/Observation\"],\"maxBatchResourceCount\":500")); + assertTrue(startRequest.getParameters().startsWith("{\"ndJsonUrls\":[\"http://example.com/Patient\",\"http://example.com/Observation\"],\"httpBasicCredentials\":\"admin:password\",\"maxBatchResourceCount\":500,\"partitionId\":{\"allPartitions\":false")); } @Test diff --git a/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/ParameterSerializationTest.java b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/ParameterSerializationTest.java new file mode 100644 index 000000000000..6ae435532bc1 --- /dev/null +++ b/hapi-fhir-storage-batch2-jobs/src/test/java/ca/uhn/fhir/batch2/jobs/imprt/ParameterSerializationTest.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.batch2.jobs.imprt; + +import ca.uhn.fhir.batch2.model.JobInstanceStartRequest; +import ca.uhn.fhir.util.JsonUtil; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +public class ParameterSerializationTest { + + @Test + public void testBatchJobParametersSuccessfullySerializeAllFields() { + JobInstanceStartRequest startRequest = new JobInstanceStartRequest(); + BulkImportJobParameters parameters = new BulkImportJobParameters(); + parameters.addNdJsonUrl("myurl"); + parameters.setHttpBasicCredentials("username:password"); + startRequest.setParameters(parameters); + + BulkImportJobParameters readBackParameters = startRequest.getParameters(BulkImportJobParameters.class); + + assertThat(readBackParameters.getHttpBasicCredentials(), is(equalTo("username:password"))); + } +} diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index 8c4c1faaf01c..3ef64804f94d 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 7a95ec517728..a5ca55bc452c 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobInstanceStartRequest.java b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobInstanceStartRequest.java index d32b42090585..607ebb5ec55d 100644 --- a/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobInstanceStartRequest.java +++ b/hapi-fhir-storage-batch2/src/main/java/ca/uhn/fhir/batch2/model/JobInstanceStartRequest.java @@ -83,7 +83,7 @@ public void setParameters(String theParameters) { } public JobInstanceStartRequest setParameters(IModelJson theParameters) { - myParameters = JsonUtil.serializeOrInvalidRequest(theParameters); + myParameters = JsonUtil.serializeWithSensitiveData(theParameters); return this; } diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index 4381648826b2..76cd2ad733cd 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index c37dbd5e27cd..1cbf84094db2 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index 2895bd7588e6..01e9b1fbbcd5 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index 860fd7343653..6613b20f882e 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 4e03f59d5e9f..aa03ab41e5cd 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index c93bc03b902a..6c8a47d61c09 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 870beb10cebf..409aa918a75c 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index cab349ea820d..8e319647ffef 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 1b423677dc58..6fab99fa4cbe 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 17dbd4743ce3..445da62e182a 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 8a70b452519b..096936ffc1e9 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 8e04fa95f365..50f87fd2754f 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 0ed01e9878d7..987b62e67869 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 8742b563b7be..4a90eb6ec0c3 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index 0ef7ba05a1da..cffe4623d220 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index d315c462cce8..8d940bc43501 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index f2c8f8042316..85c1982507fc 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index d8d271f67507..14c524c5e1a5 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 29c27cf38254..a80af4c6a26d 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 0a14f1c92115..c52a71b345f8 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 6df1a2de51e8..cb37283c6bfc 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index bf20a185b7b3..0142f72930d2 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 13f2883147e9..611613f91b84 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index c621b1701b78..7c9f695e6f24 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index 566c887a1d4a..008866c48555 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index c07a5ffff406..ab63e991255d 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.0.0-SNAPSHOT + 7.0.1-SNAPSHOT ../../pom.xml From d63d3cab46d7e961f8a2a8f958881124d6284dff Mon Sep 17 00:00:00 2001 From: Tadgh Date: Sun, 4 Feb 2024 14:45:26 -0800 Subject: [PATCH 19/24] Rel 7 0 CVE (#5663) * Bump json-path * Pin parrson * Bump elastic * Bump spring version * Exclude JDBC --- hapi-fhir-converter/pom.xml | 7 +++++++ pom.xml | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 1b7b23f8ae30..02d21d6d15ab 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -23,6 +23,13 @@ ca.uhn.hapi.fhir org.hl7.fhir.convertors ${fhir_core_version} + + + + org.xerial + sqlite-jdbc + + diff --git a/pom.xml b/pom.xml index 611613f91b84..8e9a0f2c8144 100644 --- a/pom.xml +++ b/pom.xml @@ -976,7 +976,7 @@ 2.2.19 2.0.9 2.19.0 - 6.1.1 + 6.1.3 2023.1.0 4.3.3 3.2.0 @@ -989,7 +989,7 @@ UTF-8 1.0.1 1.44.0 - 8.11.1 + 8.12.0 1.0.8 @@ -1082,7 +1082,7 @@ com.jayway.jsonpath json-path - 2.8.0 + 2.9.0 com.jayway.jsonpath @@ -1358,6 +1358,11 @@ elasticsearch-java ${elasticsearch_version} + + org.eclipse.parsson + parsson + 1.0.5 + com.google.code.gson gson From ace3fccf31dc6883a6ffcefed2287925c3568945 Mon Sep 17 00:00:00 2001 From: Tadgh Date: Mon, 5 Feb 2024 14:39:04 -0800 Subject: [PATCH 20/24] Descendent fix (#5669) * Fix "is-a" ValueSet expansion, add "descendent-of" support * Fixed tests in DSTU3 and R5 * Trigger new build * Revert "Trigger new build" This reverts commit 46c672b338db1f85c4f438344bfa828bfc723a37. * fix default partition setting on resource (#5617) * fix default partition setting on resource * changelog * Handle DEFAULT partition in rule checker. * Fix spotless --------- Co-authored-by: Michael Buckley Co-authored-by: James Agnew * pom bump, doc add, version enum add (#5616) Co-authored-by: Long Ma * fix default partition setting on resource (#5618) * fix default partition setting on resource * Handle DEFAULT partition in rule checker. Co-authored-by: Ken Stevens * Add setting to make broker not use JacksonMessageConverter (#5611) * Add setting to make broker not use JacksonMessageConverter * Add changelog * Implement suggestions --------- Co-authored-by: juan.marchionatto * Fix version * add changelog, add attribution, remove accidental bring-overs * add changelog, add attribution, remove accidental bring-overs * Finish jira section --------- Co-authored-by: Ole Hedegaard Co-authored-by: Ken Stevens Co-authored-by: Michael Buckley Co-authored-by: James Agnew Co-authored-by: longma1 <32119004+longma1@users.noreply.github.com> Co-authored-by: Long Ma Co-authored-by: jmarchionatto <60409882+jmarchionatto@users.noreply.github.com> Co-authored-by: juan.marchionatto --- .../7_0_0/5603-is-a-descendent-of.yaml | 6 ++++ .../uhn/hapi/fhir/changelog/7_2_0/upgrade.md | 0 .../hapi/fhir/changelog/7_2_0/version.yaml | 3 ++ .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 10 ++++++ ...esourceDaoR4SearchWithElasticSearchIT.java | 2 +- .../FhirResourceDaoDstu3TerminologyTest.java | 10 +++--- ...rceProviderDstu3ValueSetVersionedTest.java | 2 +- .../r4/FhirResourceDaoR4TerminologyTest.java | 36 ++++++++++++++++--- ...rceProviderR4ValueSetNoVerCSNoVerTest.java | 4 +-- ...ourceProviderR4ValueSetVerCSNoVerTest.java | 2 +- ...esourceProviderR4ValueSetVerCSVerTest.java | 2 +- .../r5/ResourceProviderR5ValueSetTest.java | 2 +- ...sourceProviderR5ValueSetVersionedTest.java | 2 +- .../channel/api/BaseChannelSettings.java | 3 ++ 14 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5603-is-a-descendent-of.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/upgrade.md create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/version.yaml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5603-is-a-descendent-of.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5603-is-a-descendent-of.yaml new file mode 100644 index 000000000000..1c8244a07ee5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5603-is-a-descendent-of.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 5603 +jira: SMILE-8000 +title: "Previously, the semantics of `is-a` were incorrect in Valueset Expansion. The implementation previously used the behaviour of `descendent-of`, which means that `A is-a A` was not being considered as true. This has been corrected. In addition, +`descendent-of` is now supported, which compares for strict descendency, and does not include itself. Thanks to Ole Hedegaard (@ohetrifork) for the fix." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/upgrade.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/upgrade.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/version.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/version.yaml new file mode 100644 index 000000000000..9e4b8d89de0d --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/version.yaml @@ -0,0 +1,3 @@ +--- +release-date: "2023-05-18" +codename: "Borealis" diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java index 4c3420ea4eaa..86235e0403be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/TermReadSvcImpl.java @@ -1598,6 +1598,16 @@ private void handleFilterConceptAndCode( TermConcept code = findCodeForFilterCriteria(theSystem, theFilter); if (theFilter.getOp() == ValueSet.FilterOperator.ISA) { + ourLog.debug( + " * Filtering on specific code and codes with a parent of {}/{}/{}", + code.getId(), + code.getCode(), + code.getDisplay()); + + b.must(f.bool() + .should(f.match().field("myParentPids").matching("" + code.getId())) + .should(f.match().field("myId").matching(code.getId()))); + } else if (theFilter.getOp() == ValueSet.FilterOperator.DESCENDENTOF) { ourLog.debug( " * Filtering on codes with a parent of {}/{}/{}", code.getId(), code.getCode(), code.getDisplay()); diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java index a17f61fe13e2..615f5d9e1652 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java +++ b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchWithElasticSearchIT.java @@ -786,7 +786,7 @@ public void testExpandWithIsAInExternalValueSet() { logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); } @Test diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java index d5c500e4b171..c5ac179edbe4 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3TerminologyTest.java @@ -313,7 +313,7 @@ public void testExpandWithCodesAndDisplayFilterBlank() { .setSystem(codeSystem.getUrl()) .addFilter() .setProperty("concept") - .setOp(FilterOperator.ISA) + .setOp(FilterOperator.DESCENDENTOF) .setValue("dogs"); myValueSetDao.create(valueSet, mySrd); @@ -504,7 +504,7 @@ public void testExpandWithIsAInExternalValueSet() { logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); } @@ -535,7 +535,7 @@ public void testExpandWithIsAInExternalValueSetReindex() { logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); } @@ -650,7 +650,7 @@ public void testIndexingIsDeferredForLargeCodeSystems() { ValueSet vs = new ValueSet(); ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(URL_MY_CODE_SYSTEM); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); ValueSet result = myValueSetDao.expand(vs, null); logAndValidateValueSet(result); @@ -669,7 +669,7 @@ public void testIndexingIsDeferredForLargeCodeSystems() { vs = new ValueSet(); include = vs.getCompose().addInclude(); include.setSystem(URL_MY_CODE_SYSTEM); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); result = myValueSetDao.expand(vs, null); logAndValidateValueSet(result); diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java index aeb4cc7328c6..98dd6fb4ec15 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3ValueSetVersionedTest.java @@ -230,7 +230,7 @@ private ValueSet createLocalVs(String theCodeSystemUrl, String theValueSetVersio ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(theCodeSystemUrl); include.setVersion(theValueSetVersion); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); return myLocalVs; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java index 048e242d7002..789e11b60f11 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TerminologyTest.java @@ -353,7 +353,7 @@ public void testExpandWithCodesAndDisplayFilterBlank() { .setSystem(codeSystem.getUrl()) .addFilter() .setProperty("concept") - .setOp(FilterOperator.ISA) + .setOp(FilterOperator.DESCENDENTOF) .setValue("dogs"); myValueSetDao.create(valueSet, mySrd); @@ -584,7 +584,7 @@ public void testExpandWithIsAInExternalValueSet() { logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); - assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); } @@ -610,6 +610,34 @@ public void testExpandWithIsAInExternalValueSetReindex() { logAndValidateValueSet(result); ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertEquals(3, codes.size()); + assertThat(codes, containsInAnyOrder("childAA", "childAAA", "childAAB")); + + } + + @Test + public void testExpandWithDescendentOfInExternalValueSetReindex() { + TermReindexingSvcImpl.setForceSaveDeferredAlwaysForUnitTest(true); + + createExternalCsAndLocalVs(); + + myResourceReindexingSvc.markAllResourcesForReindexing(); + myResourceReindexingSvc.forceReindexingPass(); + myResourceReindexingSvc.forceReindexingPass(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + myTerminologyDeferredStorageSvc.saveDeferred(); + + ValueSet vs = new ValueSet(); + ConceptSetComponent include = vs.getCompose().addInclude(); + include.setSystem(TermTestUtil.URL_MY_CODE_SYSTEM); + include.addFilter().setOp(FilterOperator.DESCENDENTOF).setValue("childAA").setProperty("concept"); + + ValueSet result = myValueSetDao.expand(vs, null); // breakpoint + logAndValidateValueSet(result); + + ArrayList codes = toCodesContains(result.getExpansion().getContains()); + assertEquals(2, codes.size()); assertThat(codes, containsInAnyOrder("childAAA", "childAAB")); } @@ -795,7 +823,7 @@ public void testIndexingIsDeferredForLargeCodeSystems() { ValueSet vs = new ValueSet(); ConceptSetComponent include = vs.getCompose().addInclude(); include.setSystem(TermTestUtil.URL_MY_CODE_SYSTEM); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); ValueSet result = myValueSetDao.expand(vs, null); logAndValidateValueSet(result); @@ -814,7 +842,7 @@ public void testIndexingIsDeferredForLargeCodeSystems() { vs = new ValueSet(); include = vs.getCompose().addInclude(); include.setSystem(TermTestUtil.URL_MY_CODE_SYSTEM); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); result = myValueSetDao.expand(vs, null); logAndValidateValueSet(result); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java index 2ab6dd76bf74..a3e9614ff32f 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetNoVerCSNoVerTest.java @@ -202,7 +202,7 @@ private void createLocalVsForCodeSystem(CodeSystem codeSystem) { myLocalVs.setUrl(URL_MY_VALUE_SET); ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(codeSystem.getUrl()); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); } @@ -1199,7 +1199,7 @@ private void createHierarchicalVs() { .setSystem(URL_MY_CODE_SYSTEM) .addFilter() .setProperty("concept") - .setOp(FilterOperator.ISA) + .setOp(FilterOperator.DESCENDENTOF) .setValue("A"); myLocalVs .getCompose() diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java index 7b56df6dbc80..cb8de80537b5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSNoVerTest.java @@ -167,7 +167,7 @@ private void createLocalVsForCodeSystem(CodeSystem codeSystem) { myLocalVs.setUrl(URL_MY_VALUE_SET); ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(codeSystem.getUrl()); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java index cc8d09d8ff79..f10efb5b1f6e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ValueSetVerCSVerTest.java @@ -196,7 +196,7 @@ private ValueSet createLocalVs(String theCodeSystemUrl, String theValueSetVersio ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(theCodeSystemUrl); include.setVersion(theValueSetVersion); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); return myLocalVs; } diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java index f5f882e38fff..9df23b883144 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetTest.java @@ -208,7 +208,7 @@ private void createLocalVs(CodeSystem codeSystem) { myLocalVs.setUrl(URL_MY_VALUE_SET); ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(codeSystem.getUrl()); - include.addFilter().setProperty("concept").setOp(Enumerations.FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(Enumerations.FilterOperator.DESCENDENTOF).setValue("ParentA"); myLocalValueSetId = myValueSetDao.create(myLocalVs, mySrd).getId().toUnqualifiedVersionless(); } diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java index 824384e57c47..c7af44cab70a 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/provider/r5/ResourceProviderR5ValueSetVersionedTest.java @@ -231,7 +231,7 @@ private ValueSet createLocalVs(String theCodeSystemUrl, String theValueSetVersio ConceptSetComponent include = myLocalVs.getCompose().addInclude(); include.setSystem(theCodeSystemUrl); include.setVersion(theValueSetVersion); - include.addFilter().setProperty("concept").setOp(FilterOperator.ISA).setValue("ParentA"); + include.addFilter().setProperty("concept").setOp(FilterOperator.DESCENDENTOF).setValue("ParentA"); return myLocalVs; } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java index 29a4b6d086e9..0776d1b8bb30 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/subscription/channel/api/BaseChannelSettings.java @@ -26,6 +26,9 @@ public abstract class BaseChannelSettings implements IChannelSettings { private ChannelRetryConfiguration myRetryConfigurationParameters; + // init true to match previous behaviour + private boolean myUseJacksonMessageConverter = true; + /** * Default true. Used by IChannelNamer to decide how to qualify the channel name. */ From 4832a5cc4795b55090fff21ed9f5cc47d8d9bd26 Mon Sep 17 00:00:00 2001 From: Long Ma Date: Tue, 6 Feb 2024 13:09:08 -0700 Subject: [PATCH 21/24] fix conflict mistake --- .../support/CommonCodeSystemsTerminologyServiceTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java index e61767fe564a..d478e88e8a14 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java @@ -14,6 +14,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.ValueSet; +import org.hl7.fhir.r5.model.Enumerations; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -303,7 +304,7 @@ public void testFetchCodeSystem_withMimeType_returnsOk() { CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem(MIMETYPES_CODESYSTEM_URL); assertNull(cs); assertTrue(cs.getConcept().isEmpty()); - assertEquals(CodeSystemContentMode.NOTPRESENT, cs.getContent()); + assertEquals(CodeSystem.CodeSystemContentMode.NOTPRESENT, cs.getContent()); } @ParameterizedTest From 27ffdfd1c1702f0c26dd079687260260e9edbca4 Mon Sep 17 00:00:00 2001 From: Long Ma Date: Wed, 7 Feb 2024 10:03:01 -0700 Subject: [PATCH 22/24] removed test assert that is no longer true: --- .../support/CommonCodeSystemsTerminologyServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java index d478e88e8a14..194020635746 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyServiceTest.java @@ -302,7 +302,6 @@ public void testGetCodeSystemUrl_forDSTU3_throwsException() { @Test public void testFetchCodeSystem_withMimeType_returnsOk() { CodeSystem cs = (CodeSystem) mySvc.fetchCodeSystem(MIMETYPES_CODESYSTEM_URL); - assertNull(cs); assertTrue(cs.getConcept().isEmpty()); assertEquals(CodeSystem.CodeSystemContentMode.NOTPRESENT, cs.getContent()); } From f1026f2660538dd4987d453657ac24e3c5fa1914 Mon Sep 17 00:00:00 2001 From: Long Ma Date: Wed, 7 Feb 2024 10:59:36 -0700 Subject: [PATCH 23/24] version bumpb to 7.1.1 --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- hapi-fhir-bom/pom.xml | 4 ++-- hapi-fhir-checkstyle/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-api/pom.xml | 2 +- hapi-fhir-cli/hapi-fhir-cli-app/pom.xml | 2 +- hapi-fhir-cli/pom.xml | 2 +- hapi-fhir-client-okhttp/pom.xml | 2 +- hapi-fhir-client/pom.xml | 2 +- hapi-fhir-converter/pom.xml | 2 +- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- hapi-fhir-jpaserver-elastic-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-hfql/pom.xml | 2 +- hapi-fhir-jpaserver-ips/pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-cds-hooks/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml | 2 +- hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml | 4 ++-- hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml | 2 +- hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml | 2 +- hapi-fhir-serviceloaders/pom.xml | 2 +- .../hapi-fhir-spring-boot-autoconfigure/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-client-apache/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-client-okhttp/pom.xml | 2 +- .../hapi-fhir-spring-boot-sample-server-jersey/pom.xml | 2 +- hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml | 2 +- hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml | 2 +- hapi-fhir-spring-boot/pom.xml | 2 +- hapi-fhir-sql-migrate/pom.xml | 2 +- hapi-fhir-storage-batch2-jobs/pom.xml | 2 +- hapi-fhir-storage-batch2-test-utilities/pom.xml | 2 +- hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-cr/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 2 +- hapi-fhir-structures-r5/pom.xml | 2 +- hapi-fhir-test-utilities/pom.xml | 2 +- hapi-fhir-testpage-overlay/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2.1/pom.xml | 2 +- hapi-fhir-validation-resources-dstu2/pom.xml | 2 +- hapi-fhir-validation-resources-dstu3/pom.xml | 2 +- hapi-fhir-validation-resources-r4/pom.xml | 2 +- hapi-fhir-validation-resources-r4b/pom.xml | 2 +- hapi-fhir-validation-resources-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml | 2 +- tests/hapi-fhir-base-test-mindeps-client/pom.xml | 2 +- tests/hapi-fhir-base-test-mindeps-server/pom.xml | 2 +- 77 files changed, 79 insertions(+), 79 deletions(-) diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 629a8c9e5e99..918767f72843 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 43446cdd944a..e7403b5cd783 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 8ff07e5a1db4..d4a5abedb1d3 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index d50ca6d1cee2..bed669c9f8c2 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -4,7 +4,7 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT pom HAPI FHIR BOM @@ -12,7 +12,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 969b20ccbfce..dcf77291f2d2 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml index 780c0ffcfa36..ce0e2b45856a 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml index b9aba8e0b26a..c465b92eb5b6 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir-cli - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 330133c4242d..e24f14917bcc 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 988e0dcc301c..fd9b7e98ce90 100644 --- a/hapi-fhir-client-okhttp/pom.xml +++ b/hapi-fhir-client-okhttp/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 973441a353c6..ba156e5587cf 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 85faf8a590d3..11f8b6819bdc 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 4586527506ff..1428c80084e8 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index e3d991624a0d..e4e109b57908 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 160ca4ca85d1..15bce39efd8e 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index af754c56fae5..9b8db8000503 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index e6e48a8e7272..61dcaf2bd438 100644 --- a/hapi-fhir-jpa/pom.xml +++ b/hapi-fhir-jpa/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 9dbfffd390e3..236abc7885f5 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index 1535dc6758a9..34fe9fd09934 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml index 8adf95cafcd8..416278d5375b 100644 --- a/hapi-fhir-jpaserver-hfql/pom.xml +++ b/hapi-fhir-jpaserver-hfql/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-ips/pom.xml b/hapi-fhir-jpaserver-ips/pom.xml index a650708ac116..fa1c47f1bc85 100644 --- a/hapi-fhir-jpaserver-ips/pom.xml +++ b/hapi-fhir-jpaserver-ips/pom.xml @@ -3,7 +3,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 88bff1548ccc..ef831441989a 100644 --- a/hapi-fhir-jpaserver-mdm/pom.xml +++ b/hapi-fhir-jpaserver-mdm/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 98f3f16b8847..48fe03cf473d 100644 --- a/hapi-fhir-jpaserver-model/pom.xml +++ b/hapi-fhir-jpaserver-model/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index dfb03772e7d7..e9687027e68c 100755 --- a/hapi-fhir-jpaserver-searchparam/pom.xml +++ b/hapi-fhir-jpaserver-searchparam/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 6e6bd6e29faa..996111256933 100644 --- a/hapi-fhir-jpaserver-subscription/pom.xml +++ b/hapi-fhir-jpaserver-subscription/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index a13ca8df9d6a..e3093404a9bf 100644 --- a/hapi-fhir-jpaserver-test-dstu2/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index ccf4ea12fc81..28b228ce9a7e 100644 --- a/hapi-fhir-jpaserver-test-dstu3/pom.xml +++ b/hapi-fhir-jpaserver-test-dstu3/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index a423489273a9..14977a58526a 100644 --- a/hapi-fhir-jpaserver-test-r4/pom.xml +++ b/hapi-fhir-jpaserver-test-r4/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index 6caf9b57ee30..b03d648b9e40 100644 --- a/hapi-fhir-jpaserver-test-r4b/pom.xml +++ b/hapi-fhir-jpaserver-test-r4b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 589514a0920d..d773feb02865 100644 --- a/hapi-fhir-jpaserver-test-r5/pom.xml +++ b/hapi-fhir-jpaserver-test-r5/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index 16b27f292e2d..0e37cc31b52d 100644 --- a/hapi-fhir-jpaserver-test-utilities/pom.xml +++ b/hapi-fhir-jpaserver-test-utilities/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 86c244dfbf8b..aea0554327b4 100644 --- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml +++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml index e54d84ed2504..d009c66cca85 100644 --- a/hapi-fhir-server-cds-hooks/pom.xml +++ b/hapi-fhir-server-cds-hooks/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index ef5a33af0c44..450e34356e38 100644 --- a/hapi-fhir-server-mdm/pom.xml +++ b/hapi-fhir-server-mdm/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 915d081dbc91..8e031e7080c4 100644 --- a/hapi-fhir-server-openapi/pom.xml +++ b/hapi-fhir-server-openapi/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 05911728343d..9809e0748541 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index 7d29c911aa4e..dca6baf7ad83 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml index c0869cd481d9..59e412a2defc 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml @@ -21,7 +21,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index ea9a1742b73c..0eab901170ce 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml @@ -7,7 +7,7 @@ hapi-fhir-serviceloaders ca.uhn.hapi.fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml index b721c4adccc7..2fdf451c1edf 100644 --- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml +++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml @@ -7,7 +7,7 @@ hapi-fhir ca.uhn.hapi.fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index 0d1b280f250d..a0894d2f8fc2 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-deployable-pom ca.uhn.hapi.fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index 4235822f44eb..1b29a4b09bda 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml index cfbea2967323..3e48f8351b09 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT hapi-fhir-spring-boot-sample-client-apache diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml index dc858d2dd456..498880b636d7 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml index b7076dd414e1..fe9bcd159dde 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot-samples - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml index 66adce27094e..aa40eb421144 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir-spring-boot - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml index c13b482b2914..4badcc6c176a 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 33b7eaeecbdc..5a41d6c46b40 100644 --- a/hapi-fhir-spring-boot/pom.xml +++ b/hapi-fhir-spring-boot/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 2e4860c5057e..e31123d2dcb7 100644 --- a/hapi-fhir-sql-migrate/pom.xml +++ b/hapi-fhir-sql-migrate/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml index 14cc4359d72c..fef1928bb754 100644 --- a/hapi-fhir-storage-batch2-jobs/pom.xml +++ b/hapi-fhir-storage-batch2-jobs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml index 167438582182..9a6bcb370d0d 100644 --- a/hapi-fhir-storage-batch2-test-utilities/pom.xml +++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 2afd5c63f894..aa59e2eb6e2f 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml index 6c432220dbba..549092aa7844 100644 --- a/hapi-fhir-storage-cr/pom.xml +++ b/hapi-fhir-storage-cr/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index a542f34b632b..6426ed4e4718 100644 --- a/hapi-fhir-storage-mdm/pom.xml +++ b/hapi-fhir-storage-mdm/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index c83f11865261..a8e0e56d2d24 100644 --- a/hapi-fhir-storage-test-utilities/pom.xml +++ b/hapi-fhir-storage-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index fa2c439f96a5..cd2e9954c0da 100644 --- a/hapi-fhir-storage/pom.xml +++ b/hapi-fhir-storage/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 5b11dc8e8815..8c2399d6ca66 100644 --- a/hapi-fhir-structures-dstu2.1/pom.xml +++ b/hapi-fhir-structures-dstu2.1/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index 9c0eb67b6d94..727e5fbe3ebc 100644 --- a/hapi-fhir-structures-dstu2/pom.xml +++ b/hapi-fhir-structures-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 30eff20d18a8..ce266f385746 100644 --- a/hapi-fhir-structures-dstu3/pom.xml +++ b/hapi-fhir-structures-dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index 9bcf9ec681bf..339e3a56a750 100644 --- a/hapi-fhir-structures-hl7org-dstu2/pom.xml +++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 8d3e9b2daf85..678936c9d256 100644 --- a/hapi-fhir-structures-r4/pom.xml +++ b/hapi-fhir-structures-r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 4e1281705343..45a31cee715d 100644 --- a/hapi-fhir-structures-r4b/pom.xml +++ b/hapi-fhir-structures-r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 5cfb4ddb7b3b..54e15932b336 100644 --- a/hapi-fhir-structures-r5/pom.xml +++ b/hapi-fhir-structures-r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 1b2713493d4b..c5a75f45a854 100644 --- a/hapi-fhir-test-utilities/pom.xml +++ b/hapi-fhir-test-utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index cf420ee8e11f..92c22dade3ca 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index db0343305dab..d8760d97eb80 100644 --- a/hapi-fhir-validation-resources-dstu2.1/pom.xml +++ b/hapi-fhir-validation-resources-dstu2.1/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu2/pom.xml b/hapi-fhir-validation-resources-dstu2/pom.xml index e08aaffcd09b..6ba13f2f4efa 100644 --- a/hapi-fhir-validation-resources-dstu2/pom.xml +++ b/hapi-fhir-validation-resources-dstu2/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-dstu3/pom.xml b/hapi-fhir-validation-resources-dstu3/pom.xml index 595a06b7d756..11a85020fd43 100644 --- a/hapi-fhir-validation-resources-dstu3/pom.xml +++ b/hapi-fhir-validation-resources-dstu3/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4/pom.xml b/hapi-fhir-validation-resources-r4/pom.xml index 98966449e16d..0dd048fce72a 100644 --- a/hapi-fhir-validation-resources-r4/pom.xml +++ b/hapi-fhir-validation-resources-r4/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r4b/pom.xml b/hapi-fhir-validation-resources-r4b/pom.xml index 2ce153ceca0a..e136f7f069bc 100644 --- a/hapi-fhir-validation-resources-r4b/pom.xml +++ b/hapi-fhir-validation-resources-r4b/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation-resources-r5/pom.xml b/hapi-fhir-validation-resources-r5/pom.xml index 9024474ad683..30bf5f6f9a0d 100644 --- a/hapi-fhir-validation-resources-r5/pom.xml +++ b/hapi-fhir-validation-resources-r5/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 4d0c094552f2..df26b6a2b560 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 8459ead2709d..7061c79998fa 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index fa628d257f89..74d386e104df 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ce4d3d980b7e..177b96d5df2a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 05def91802ec..7f6b718f01d4 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-client/pom.xml b/tests/hapi-fhir-base-test-mindeps-client/pom.xml index 340b817cf11d..b71fbc1a6bc6 100644 --- a/tests/hapi-fhir-base-test-mindeps-client/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../pom.xml diff --git a/tests/hapi-fhir-base-test-mindeps-server/pom.xml b/tests/hapi-fhir-base-test-mindeps-server/pom.xml index 7aaa85424307..d2c565be41eb 100644 --- a/tests/hapi-fhir-base-test-mindeps-server/pom.xml +++ b/tests/hapi-fhir-base-test-mindeps-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 7.1.0-SNAPSHOT + 7.1.1-SNAPSHOT ../../pom.xml From 70a66a7063899011256fb506058187d727bb645e Mon Sep 17 00:00:00 2001 From: Long Ma Date: Wed, 7 Feb 2024 12:29:36 -0700 Subject: [PATCH 24/24] fix version Enum test --- .../src/test/java/ca/uhn/fhir/util/VersionEnumTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java index b0d8c5bf8a44..de6daf072d89 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/VersionEnumTest.java @@ -29,7 +29,7 @@ public void testCurrentVersionExists() { int minor = Integer.parseInt(parts[1]); int patch = Integer.parseInt(parts[2]); - if (major >= 6 && minor >= 3) { + if ((major == 6 && minor >= 3) || major > 6 ) { if (minor % 2 == 1) { patch = 0; }