From c3697a5f87c513b9a958375336a7fd30792291c0 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 24 Nov 2022 14:32:56 -0500 Subject: [PATCH 1/2] JPA Cleanup (#4278) * JPA cleanup * Test fixes * Cleanup * Resolve fixme * Fix bad changelog entry * A bit of documentation reworking * Version bump * POM bump * Avoid timeout * Build fix * HTMLUnit bump --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../model/api/ResourceMetadataKeyEnum.java | 38 +- .../java/ca/uhn/fhir/util/ClasspathUtil.java | 7 + .../java/ca/uhn/fhir/util/VersionEnum.java | 1 + .../ca/uhn/fhir/i18n/hapi-messages.properties | 2 +- .../ca/uhn/fhir/util/ClasspathUtilTest.java | 9 + .../ca/uhn/fhir/util/VersionEnumTest.java | 18 +- 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/hapi-fhir-cli-jpaserver/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 +- .../canonical/VersionCanonicalizer.java | 75 +++- hapi-fhir-dist/pom.xml | 2 +- hapi-fhir-docs/pom.xml | 2 +- .../hapi/fhir/changelog/5_7_7/version.yaml | 2 +- .../hapi/fhir/changelog/6_3_0/changes.yaml | 1 + .../docs/interceptors/server_pointcuts.md | 8 +- .../interceptors-server-jpa-pipeline.svg | 0 .../interceptors-server-pipeline.svg | 0 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 +- .../jpa/batch2/JpaJobPersistenceImpl.java | 8 +- .../ca/uhn/fhir/jpa/config/JpaConfig.java | 23 ++ .../jpa/config/SharedConfigDstu3Plus.java | 55 --- .../fhir/jpa/config/dstu3/JpaDstu3Config.java | 2 - .../uhn/fhir/jpa/config/r4/JpaR4Config.java | 3 - .../uhn/fhir/jpa/config/r4b/JpaR4BConfig.java | 2 - .../uhn/fhir/jpa/config/r5/JpaR5Config.java | 2 - .../dao/FhirResourceDaoCompositionDstu2.java | 42 -- .../FhirResourceDaoSearchParameterDstu2.java | 89 ----- ...R4.java => JpaResourceDaoComposition.java} | 6 +- ...on.java => JpaResourceDaoObservation.java} | 53 ++- ...ava => JpaResourceDaoSearchParameter.java} | 83 ++-- .../FhirResourceDaoCompositionDstu3.java | 59 --- .../FhirResourceDaoObservationDstu3.java | 83 ---- .../FhirResourceDaoSearchParameterDstu3.java | 80 ---- .../dao/r4/FhirResourceDaoObservationR4.java | 88 ----- .../r4b/FhirResourceDaoCompositionR4B.java | 59 --- .../r4b/FhirResourceDaoObservationR4B.java | 83 ---- .../FhirResourceDaoSearchParameterR4B.java | 83 ---- .../dao/r5/FhirResourceDaoCompositionR5.java | 59 --- .../dao/r5/FhirResourceDaoObservationR5.java | 83 ---- .../r5/FhirResourceDaoSearchParameterR5.java | 79 ---- hapi-fhir-jpaserver-cql/pom.xml | 2 +- .../java/ca/uhn/fhir/cql/BaseCqlR4Test.java | 6 - .../pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- .../SearchParameterCanonicalizer.java | 56 +-- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- .../SubscriptionValidatingInterceptor.java | 8 +- hapi-fhir-jpaserver-test-dstu2/pom.xml | 2 +- .../fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java | 12 +- .../jpa/provider/SubscriptionsDstu2Test.java | 5 +- hapi-fhir-jpaserver-test-dstu3/pom.xml | 2 +- ...ceDaoDstu3SearchCustomSearchParamTest.java | 52 ++- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- .../fhir/jpa/batch2/Batch2CoordinatorIT.java | 12 - ...=> JpaResourceDaoSearchParameterTest.java} | 16 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- .../uhn/fhir/jpa/dao/r4b/BaseJpaR4BTest.java | 7 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- .../ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java | 191 +-------- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- .../uhn/fhir/jpa/test/BaseJpaDstu3Test.java | 5 +- .../ca/uhn/fhir/jpa/test/BaseJpaR4Test.java | 8 +- .../uhn/fhir/jpa/testutil/SpringFileTest.java | 15 - hapi-fhir-jpaserver-uhnfhirtest/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 +- hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- .../dao/MetadataKeyCurrentlyReindexing.java | 21 +- .../jpa/dao/BaseTransactionProcessor.java | 7 +- hapi-fhir-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 29 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-r4b/pom.xml | 13 +- 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-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- hapi-tinder-plugin/pom.xml | 2 +- .../fhir/tinder/TinderJpaRestServerMojo.java | 34 +- .../src/main/resources/vm/client.vm | 70 ---- .../main/resources/vm/dt_composite_dstu.vm | 233 ----------- .../src/main/resources/vm/jpa_spring_beans.vm | 68 ---- .../resources/vm/jpa_spring_beans_java.vm | 11 +- .../src/main/resources/vm/resource_dstu.vm | 99 ----- .../src/main/resources/vm/templates_dstu.vm | 361 ------------------ hapi-tinder-test/pom.xml | 2 +- pom.xml | 26 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 129 files changed, 454 insertions(+), 2340 deletions(-) rename hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/{images => interceptors/server_pointcuts}/interceptors-server-jpa-pipeline.svg (100%) rename hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/{images => interceptors/server_pointcuts}/interceptors-server-pipeline.svg (100%) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/SharedConfigDstu3Plus.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/{r4/FhirResourceDaoCompositionR4.java => JpaResourceDaoComposition.java} (90%) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/{BaseHapiFhirResourceDaoObservation.java => JpaResourceDaoObservation.java} (73%) rename hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/{r4/FhirResourceDaoSearchParameterR4.java => JpaResourceDaoSearchParameter.java} (62%) delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCompositionDstu3.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoCompositionR4B.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoObservationR4B.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoSearchParameterR4B.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCompositionR5.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java delete mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java rename hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/{r4/FhirResourceDaoSearchParameterR4Test.java => JpaResourceDaoSearchParameterTest.java} (82%) delete mode 100644 hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/testutil/SpringFileTest.java delete mode 100644 hapi-tinder-plugin/src/main/resources/vm/client.vm delete mode 100644 hapi-tinder-plugin/src/main/resources/vm/dt_composite_dstu.vm delete mode 100644 hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm delete mode 100644 hapi-tinder-plugin/src/main/resources/vm/resource_dstu.vm delete mode 100644 hapi-tinder-plugin/src/main/resources/vm/templates_dstu.vm diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 47494c203c2..17da3b8c614 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 4a78ea60bf1..cff510d154e 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index cce36d7979e..03b77f17e1a 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java index 549e74d0bb3..d2d8d30d062 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ResourceMetadataKeyEnum.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import java.io.Serializable; @@ -64,6 +65,23 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; */ public abstract class ResourceMetadataKeyEnum implements Serializable { + // TODO: JA - Replace all of the various other get/put methods in subclasses with just using the two that are here + public T get(IBaseResource theResource) { + if (theResource instanceof IAnyResource) { + return (T) theResource.getUserData(name()); + } else { + return (T) ((IResource)theResource).getResourceMetadata().get(this); + } + } + + public void put(IBaseResource theResource, T theValue) { + if (theResource instanceof IAnyResource) { + theResource.setUserData(name(), theValue); + } else { + ((IResource)theResource).getResourceMetadata().put(this, theValue); + } + } + /** * If present and populated with a date/time (as an instance of {@link InstantDt}), this value is an indication that the resource is in the deleted state. This key is only used in a limited number * of scenarios, such as POSTing transaction bundles to a server, or returning resource history. @@ -71,29 +89,19 @@ public abstract class ResourceMetadataKeyEnum implements Serializable { * Values for this key are of type {@link InstantDt} *

*/ - public static final ResourceMetadataKeySupportingAnyResource> DELETED_AT = new ResourceMetadataKeySupportingAnyResource>("DELETED_AT") { + public static final ResourceMetadataKeyEnum> DELETED_AT = new ResourceMetadataKeyEnum<>("DELETED_AT") { private static final long serialVersionUID = 1L; @Override - public InstantDt get(IResource theResource) { - return getInstantFromMetadataOrNullIfNone(theResource.getResourceMetadata(), DELETED_AT); - } - - @SuppressWarnings("unchecked") - @Override - public IPrimitiveType get(IAnyResource theResource) { - return (IPrimitiveType) theResource.getUserData(DELETED_AT.name()); + public IPrimitiveType get(IResource theResource) { + return (IPrimitiveType) theResource.getResourceMetadata().get(this); } @Override - public void put(IResource theResource, InstantDt theObject) { - theResource.getResourceMetadata().put(DELETED_AT, theObject); + public void put(IResource theResource, IPrimitiveType theObject) { + theResource.getResourceMetadata().put(this, theObject); } - @Override - public void put(IAnyResource theResource, IPrimitiveType theObject) { - theResource.setUserData(DELETED_AT.name(), theObject); - } }; /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java index 199254caf1d..00dc30d9168 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java @@ -34,6 +34,9 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.function.Function; import java.util.zip.GZIPInputStream; @@ -86,6 +89,10 @@ public class ClasspathUtil { return retVal; } + public static Reader loadResourceAsReader(String theClasspath) { + return new InputStreamReader(loadResourceAsStream(theClasspath), StandardCharsets.UTF_8); + } + /** * Load a classpath resource, throw an {@link InternalErrorException} if not found */ 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 90afa7d8603..58b3143b6f0 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 @@ -107,6 +107,7 @@ public enum VersionEnum { V6_1_4, V6_2_0, V6_2_1, + // Dev Build V6_3_0 ; diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 455ee02f3a6..fa7558e5fb0 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -130,7 +130,7 @@ ca.uhn.fhir.jpa.search.builder.predicate.TokenPredicateBuilder.invalidCodeMissin ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.matchesFound=Matches found ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl.noMatchesFound=No Matches found -ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1} +ca.uhn.fhir.jpa.dao.JpaResourceDaoSearchParameter.invalidSearchParamExpression=The expression "{0}" can not be evaluated and may be invalid: {1} ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForSearchParam=The :text modifier is disabled for this search parameter ca.uhn.fhir.jpa.search.builder.QueryStack.textModifierDisabledForServer=The :text modifier is disabled on this server diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java index f0d283f8205..2d480cfb8f3 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/ClasspathUtilTest.java @@ -32,6 +32,15 @@ public class ClasspathUtilTest { } } + @Test + public void testLoadResourceAsReaderNotFound() { + try { + ClasspathUtil.loadResourceAsReader("/FOOOOOO"); + } catch (InternalErrorException e) { + assertEquals(Msg.code(1758) + "Unable to find classpath resource: /FOOOOOO", e.getMessage()); + } + } + /** * Should not throw any exception */ 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 d1dbf7ef575..b0d8c5bf8a4 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 @@ -8,6 +8,7 @@ import java.util.stream.Collectors; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; public class VersionEnumTest { @@ -18,10 +19,25 @@ public class VersionEnumTest { .collect(Collectors.toList()); String version = VersionUtil.getVersion(); - version = "V" + version.replace(".", "_"); + version = version.replaceAll("-PRE[0-9]+", ""); version = version.replace("-SNAPSHOT", ""); + String[] parts = version.split("\\."); + assertEquals(3, parts.length); + int major = Integer.parseInt(parts[0]); + int minor = Integer.parseInt(parts[1]); + int patch = Integer.parseInt(parts[2]); + + if (major >= 6 && minor >= 3) { + if (minor % 2 == 1) { + patch = 0; + } + } + version = major + "." + minor + "." + patch; + + version = "V" + version.replace(".", "_"); + assertThat(versions, hasItem(version)); } diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 26dece1bad3..56b5d5fd7c0 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -3,14 +3,14 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT pom HAPI FHIR BOM ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index 102c8d05f98..a9a5cecf5e8 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.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 4438b06e2a5..b7730bbc885 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.3.0-PRE5-SNAPSHOT + 6.3.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 c06f3c80624..0ed4c30293e 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index ce2ded17b1c..6a108f56b8b 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../../hapi-deployable-pom diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 70082a0bfb5..efc6dd0427f 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index acacb532a70..c8ba145dea2 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index e782b4908bf..599deda15a2 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 578544de925..a173b06c4c7 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java index 842b52834c8..640a29a1628 100644 --- a/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java +++ b/hapi-fhir-converter/src/main/java/ca/uhn/hapi/converters/canonical/VersionCanonicalizer.java @@ -47,9 +47,12 @@ import org.hl7.fhir.r4.model.Coding; import org.hl7.fhir.r4.model.ConceptMap; import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r5.model.CapabilityStatement; +import org.hl7.fhir.r5.model.SearchParameter; import java.util.List; +import static org.apache.commons.lang3.StringUtils.isBlank; + /** * This class converts versions of various resources to/from a canonical version * of the resource. The specific version that is considered canonical is arbitrary @@ -68,14 +71,13 @@ public class VersionCanonicalizer { private static final BaseAdvisor_10_50 ADVISOR_10_50 = new BaseAdvisor_10_50(false); private static final BaseAdvisor_40_50 ADVISOR_40_50 = new BaseAdvisor_40_50(false); private static final BaseAdvisor_43_50 ADVISOR_43_50 = new BaseAdvisor_43_50(false); - @SuppressWarnings("rawtypes") private final IStrategy myStrategy; public VersionCanonicalizer(FhirContext theTargetContext) { this(theTargetContext.getVersion().getVersion()); } - @SuppressWarnings("EnumSwitchStatementWhichMissesCases") + @SuppressWarnings({"EnumSwitchStatementWhichMissesCases", "EnhancedSwitchMigration"}) public VersionCanonicalizer(FhirVersionEnum theTargetVersion) { switch (theTargetVersion) { case DSTU2: @@ -160,9 +162,13 @@ public class VersionCanonicalizer { return myStrategy.conceptMapToCanonical(theConceptMap); } - private interface IStrategy { + public SearchParameter searchParameterToCanonical(T theSearchParameter) { + return myStrategy.searchParameterToCanonical(theSearchParameter); + } - CapabilityStatement capabilityStatementToCanonical(T theCapabilityStatement); + private interface IStrategy { + + CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement); Coding codingToCanonical(IBaseCoding theCoding); @@ -175,9 +181,11 @@ public class VersionCanonicalizer { IBaseResource valueSetFromCanonical(ValueSet theValueSet); ConceptMap conceptMapToCanonical(IBaseResource theConceptMap); + + SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter); } - private class Dstu2Strategy implements IStrategy { + private static class Dstu2Strategy implements IStrategy { private final FhirContext myDstu2Hl7OrgContext = FhirContext.forDstu2Hl7OrgCached(); @@ -189,7 +197,7 @@ public class VersionCanonicalizer { } @Override - public CapabilityStatement capabilityStatementToCanonical(ca.uhn.fhir.model.dstu2.resource.BaseResource theCapabilityStatement) { + public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) { org.hl7.fhir.dstu2.model.Resource reencoded = reencodeToHl7Org(theCapabilityStatement); return (CapabilityStatement) VersionConvertorFactory_10_50.convertResource(reencoded, ADVISOR_10_50); } @@ -224,8 +232,7 @@ public class VersionCanonicalizer { @Override public ValueSet valueSetToCanonical(IBaseResource theValueSet) { org.hl7.fhir.dstu2.model.Resource reencoded = reencodeToHl7Org(theValueSet); - ValueSet valueSet = (ValueSet) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40); - return valueSet; + return (ValueSet) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40); } @Override @@ -275,6 +282,16 @@ public class VersionCanonicalizer { return (ConceptMap) VersionConvertorFactory_10_40.convertResource(reencoded, ADVISOR_10_40); } + @Override + public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) { + org.hl7.fhir.dstu2.model.SearchParameter reencoded = (org.hl7.fhir.dstu2.model.SearchParameter) reencodeToHl7Org(theSearchParameter); + SearchParameter retVal = (SearchParameter) VersionConvertorFactory_10_50.convertResource(reencoded, ADVISOR_10_50); + if (isBlank(retVal.getExpression())) { + retVal.setExpression(reencoded.getXpath()); + } + return retVal; + } + private Resource reencodeToHl7Org(IBaseResource theInput) { if (myHl7OrgStructures) { return (Resource) theInput; @@ -291,11 +308,11 @@ public class VersionCanonicalizer { } - private class Dstu3Strategy implements IStrategy { + private static class Dstu3Strategy implements IStrategy { @Override - public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.dstu3.model.Resource theCapabilityStatement) { - return (CapabilityStatement) VersionConvertorFactory_30_50.convertResource(theCapabilityStatement, ADVISOR_30_50); + public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) { + return (CapabilityStatement) VersionConvertorFactory_30_50.convertResource((org.hl7.fhir.dstu3.model.Resource) theCapabilityStatement, ADVISOR_30_50); } @Override @@ -327,12 +344,17 @@ public class VersionCanonicalizer { public ConceptMap conceptMapToCanonical(IBaseResource theConceptMap) { return (ConceptMap) VersionConvertorFactory_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theConceptMap, ADVISOR_30_40); } + + @Override + public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) { + return (SearchParameter) VersionConvertorFactory_30_50.convertResource((org.hl7.fhir.dstu3.model.Resource) theSearchParameter, ADVISOR_30_50); + } } - private class R4Strategy implements IStrategy { + private static class R4Strategy implements IStrategy { @Override - public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r4.model.Resource theCapabilityStatement) { - return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource(theCapabilityStatement, ADVISOR_40_50); + public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) { + return (CapabilityStatement) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r4.model.Resource) theCapabilityStatement, ADVISOR_40_50); } @Override @@ -365,13 +387,18 @@ public class VersionCanonicalizer { return (ConceptMap) theConceptMap; } + @Override + public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) { + return (SearchParameter) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r4.model.Resource) theSearchParameter, ADVISOR_40_50); + } + } - private class R4BStrategy implements IStrategy { + private static class R4BStrategy implements IStrategy { @Override - public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r4b.model.Resource theCapabilityStatement) { - return (CapabilityStatement) VersionConvertorFactory_43_50.convertResource(theCapabilityStatement, ADVISOR_43_50); + public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) { + return (CapabilityStatement) VersionConvertorFactory_43_50.convertResource((org.hl7.fhir.r4b.model.Resource) theCapabilityStatement, ADVISOR_43_50); } @Override @@ -410,13 +437,18 @@ public class VersionCanonicalizer { return (ConceptMap) VersionConvertorFactory_40_50.convertResource(conceptMapR5, ADVISOR_40_50); } + @Override + public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) { + return (SearchParameter) VersionConvertorFactory_43_50.convertResource((org.hl7.fhir.r4b.model.Resource) theSearchParameter, ADVISOR_43_50); + } + } - private class R5Strategy implements IStrategy { + private static class R5Strategy implements IStrategy { @Override - public CapabilityStatement capabilityStatementToCanonical(org.hl7.fhir.r5.model.Resource theCapabilityStatement) { + public CapabilityStatement capabilityStatementToCanonical(IBaseResource theCapabilityStatement) { return (CapabilityStatement) theCapabilityStatement; } @@ -450,6 +482,11 @@ public class VersionCanonicalizer { return (ConceptMap) VersionConvertorFactory_40_50.convertResource((org.hl7.fhir.r5.model.ConceptMap) theConceptMap, ADVISOR_40_50); } + @Override + public SearchParameter searchParameterToCanonical(IBaseResource theSearchParameter) { + return (SearchParameter) theSearchParameter; + } + } diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 72205e3c27d..e30f11dc432 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index 6f79396cb4e..f9b1202c936 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_7/version.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_7/version.yaml index c086cd11a8c..b6444e8dfaa 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_7/version.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_7_7/version.yaml @@ -1,3 +1,3 @@ --- -release-date: "FILL ME" +release-date: "2022-07-08" codename: "Sojourner" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/changes.yaml index 638cff5d098..2014d019005 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/changes.yaml +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/changes.yaml @@ -6,6 +6,7 @@
  • SLF4j (All): 1.7.33 -> 2.0.3
  • Logback (All): 1.2.10 -> 1.4.4
  • +
  • Woodstox-Core (XML Parser): 6.3.1 -> 6.4.0
  • log4j-to-slf4j (JPA): 2.17.1 -> 2.19.0
  • Jetty (CLI): 9.4.48.v20220622 -> 10.0.12
  • Spring Boot: 2.7.4 -> 2.7.5
  • diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts.md index 71134c7038e..787a0f84c2f 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts.md @@ -2,13 +2,13 @@ The following diagram shows the request processing pipeline for processing a server request. You may click on the diagram to enlarge it. -Expand -Server Pipeline +Expand +Server Pipeline # Storage / JPA Server Pointcuts The following diagram shows the request processing pipeline for processing a server request. You may click on the diagram to enlarge it. -Expand -Server Pipeline +Expand +Server Pipeline diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/interceptors-server-jpa-pipeline.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts/interceptors-server-jpa-pipeline.svg similarity index 100% rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/interceptors-server-jpa-pipeline.svg rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts/interceptors-server-jpa-pipeline.svg diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/interceptors-server-pipeline.svg b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts/interceptors-server-pipeline.svg similarity index 100% rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/images/interceptors-server-pipeline.svg rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/server_pointcuts/interceptors-server-pipeline.svg diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 5c6003337ae..e839b35abb7 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index b7c7b774506..3cbf7c8c81d 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index 08d16f554dc..367298467b4 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 8b5babed5aa..41eacea7e0f 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImpl.java index 79b331e20e9..0001cd48c48 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/batch2/JpaJobPersistenceImpl.java @@ -84,7 +84,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence { } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRED) public String storeWorkChunk(BatchWorkChunk theBatchWorkChunk) { Batch2WorkChunkEntity entity = new Batch2WorkChunkEntity(); entity.setId(UUID.randomUUID().toString()); @@ -102,7 +102,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence { } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRED) public Optional fetchWorkChunkSetStartTimeAndMarkInProgress(String theChunkId) { myWorkChunkRepository.updateChunkStatusForStart(theChunkId, new Date(), StatusEnum.IN_PROGRESS); Optional chunk = myWorkChunkRepository.findById(theChunkId); @@ -110,7 +110,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence { } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRED) public String storeNewInstance(JobInstance theInstance) { Validate.isTrue(isBlank(theInstance.getInstanceId())); @@ -271,7 +271,7 @@ public class JpaJobPersistenceImpl implements IJobPersistence { } @Override - @Transactional(propagation = Propagation.REQUIRES_NEW) + @Transactional(propagation = Propagation.REQUIRED) public boolean canAdvanceInstanceToNextStep(String theInstanceId, String theCurrentStepId) { List statusesForStep = myWorkChunkRepository.getDistinctStatusesForStep(theInstanceId, theCurrentStepId); ourLog.debug("Checking whether gated job can advanced to next step. [instanceId={}, currentStepId={}, statusesForStep={}]", theInstanceId, theCurrentStepId, statusesForStep); 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 e192a468c55..207f69f94a5 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 @@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.MatchResourceUrlService; +import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.expunge.ExpungeEverythingService; @@ -116,10 +117,14 @@ import ca.uhn.fhir.jpa.searchparam.nickname.NicknameInterceptor; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl; +import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; import ca.uhn.fhir.jpa.term.TermConceptMappingSvcImpl; import ca.uhn.fhir.jpa.term.TermReadSvcImpl; +import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; +import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermConceptMappingSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; +import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; import ca.uhn.fhir.jpa.term.config.TermCodeSystemConfig; import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.jpa.validation.ResourceLoaderImpl; @@ -763,4 +768,22 @@ public class JpaConfig { return new TermReadSvcImpl(); } + @Bean + public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() { + return new TermCodeSystemStorageSvcImpl(); + } + + + + @Bean + public ITermReindexingSvc termReindexingSvc() { + return new TermReindexingSvcImpl(); + } + + @Bean + public ObservationLastNIndexPersistSvc baseObservationLastNIndexpersistSvc() { + return new ObservationLastNIndexPersistSvc(); + } + + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/SharedConfigDstu3Plus.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/SharedConfigDstu3Plus.java deleted file mode 100644 index b5a292e87a0..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/SharedConfigDstu3Plus.java +++ /dev/null @@ -1,55 +0,0 @@ -package ca.uhn.fhir.jpa.config; - -/*- - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.jpa.dao.ObservationLastNIndexPersistSvc; -import ca.uhn.fhir.jpa.term.TermCodeSystemStorageSvcImpl; -import ca.uhn.fhir.jpa.term.TermConceptDaoSvc; -import ca.uhn.fhir.jpa.term.TermDeferredStorageSvcImpl; -import ca.uhn.fhir.jpa.term.TermReindexingSvcImpl; -import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; -import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; -import ca.uhn.fhir.jpa.term.api.ITermReindexingSvc; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class SharedConfigDstu3Plus { - - @Bean - public ITermCodeSystemStorageSvc termCodeSystemStorageSvc() { - return new TermCodeSystemStorageSvcImpl(); - } - - - - @Bean - public ITermReindexingSvc termReindexingSvc() { - return new TermReindexingSvcImpl(); - } - - @Bean - public ObservationLastNIndexPersistSvc baseObservationLastNIndexpersistSvc() { - return new ObservationLastNIndexPersistSvc(); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/JpaDstu3Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/JpaDstu3Config.java index 271f288fc00..00f483f2492 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/JpaDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/dstu3/JpaDstu3Config.java @@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigDstu3; import ca.uhn.fhir.jpa.config.JpaConfig; -import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter; import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; @@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Import({ FhirContextDstu3Config.class, GeneratedDaoAndResourceProviderConfigDstu3.class, - SharedConfigDstu3Plus.class, JpaConfig.class }) public class JpaDstu3Config { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/JpaR4Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/JpaR4Config.java index 21f12606c4d..dfe7feb503e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/JpaR4Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4/JpaR4Config.java @@ -7,7 +7,6 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4; import ca.uhn.fhir.jpa.config.JpaConfig; -import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter; import ca.uhn.fhir.jpa.dao.r4.TransactionProcessorVersionAdapterR4; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; @@ -22,7 +21,6 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermLoaderSvc; import ca.uhn.fhir.jpa.term.api.ITermVersionAdapterSvc; -import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Consent; @@ -62,7 +60,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Import({ FhirContextR4Config.class, GeneratedDaoAndResourceProviderConfigR4.class, - SharedConfigDstu3Plus.class, JpaConfig.class }) public class JpaR4Config { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4b/JpaR4BConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4b/JpaR4BConfig.java index 4ccd56a15bd..c1bd1310877 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4b/JpaR4BConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r4b/JpaR4BConfig.java @@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR4B; import ca.uhn.fhir.jpa.config.JpaConfig; -import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter; import ca.uhn.fhir.jpa.dao.r4b.TransactionProcessorVersionAdapterR4B; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; @@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Import({ FhirContextR4BConfig.class, GeneratedDaoAndResourceProviderConfigR4B.class, - SharedConfigDstu3Plus.class, JpaConfig.class }) public class JpaR4BConfig { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/JpaR5Config.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/JpaR5Config.java index d2a316e19fd..ebfc1548a95 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/JpaR5Config.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/r5/JpaR5Config.java @@ -6,7 +6,6 @@ import ca.uhn.fhir.jpa.api.IDaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.config.GeneratedDaoAndResourceProviderConfigR5; import ca.uhn.fhir.jpa.config.JpaConfig; -import ca.uhn.fhir.jpa.config.SharedConfigDstu3Plus; import ca.uhn.fhir.jpa.dao.ITransactionProcessorVersionAdapter; import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5; import ca.uhn.fhir.jpa.graphql.GraphQLProvider; @@ -53,7 +52,6 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; @Import({ FhirContextR5Config.class, GeneratedDaoAndResourceProviderConfigR5.class, - SharedConfigDstu3Plus.class, JpaConfig.class }) public class JpaR5Config { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java deleted file mode 100644 index bab5c1ab2da..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoCompositionDstu2.java +++ /dev/null @@ -1,42 +0,0 @@ -package ca.uhn.fhir.jpa.dao; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.i18n.Msg; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; -import ca.uhn.fhir.model.dstu2.resource.Composition; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import javax.servlet.http.HttpServletRequest; - -public class FhirResourceDaoCompositionDstu2 extends BaseHapiFhirResourceDaoimplements IFhirResourceDaoComposition { - - @Override - public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, IPrimitiveType theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) { - throw new NotImplementedOperationException(Msg.code(955) + "$document not implemented in DSTU2"); - } -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java deleted file mode 100644 index b75d3ba3b83..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoSearchParameterDstu2.java +++ /dev/null @@ -1,89 +0,0 @@ -package ca.uhn.fhir.jpa.dao; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.model.dstu2.resource.SearchParameter; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import com.google.common.collect.Lists; -import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_10_40; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_40; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - - @Autowired - private ISearchParamExtractor mySearchParamExtractor; - private FhirContext myDstu2Hl7OrgContext = FhirContext.forDstu2Hl7Org(); - - protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) { - Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; - String expression = theResource != null ? theResource.getXpath() : null; - List base = theResource != null ? Lists.newArrayList(theResource.getBase()) : null; - requestReindexForRelatedResources(reindex, base, theRequestDetails); - } - - @Override - protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postPersist(theEntity, theResource, theRequestDetails); - reindexAffectedResources(theResource, theRequestDetails); - - } - - @Override - protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postUpdate(theEntity, theResource, theRequestDetails); - reindexAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { - super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); - reindexAffectedResources(theResourceToDelete, theRequestDetails); - } - - @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { - super.validateResourceForStorage(theResource, theEntityToSave); - - String encoded = getContext().newJsonParser().encodeResourceToString(theResource); - org.hl7.fhir.dstu2.model.SearchParameter hl7Org = myDstu2Hl7OrgContext.newJsonParser().parseResource(org.hl7.fhir.dstu2.model.SearchParameter.class, encoded); - - org.hl7.fhir.r4.model.SearchParameter convertedSp = (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_10_40.convertResource(hl7Org, new BaseAdvisor_10_40(false)); - if (isBlank(convertedSp.getExpression()) && isNotBlank(hl7Org.getXpath())) { - convertedSp.setExpression(hl7Org.getXpath()); - } - - FhirResourceDaoSearchParameterR4.validateSearchParam(convertedSp, getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor); - - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCompositionR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoComposition.java similarity index 90% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCompositionR4.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoComposition.java index 52862ef7d99..4875ed0652e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCompositionR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoComposition.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.dao.r4; +package ca.uhn.fhir.jpa.dao; /* * #%L @@ -21,7 +21,6 @@ package ca.uhn.fhir.jpa.dao.r4; */ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.api.SortSpec; @@ -29,6 +28,7 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.StringParam; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Composition; @@ -36,7 +36,7 @@ import org.hl7.fhir.r4.model.Composition; import javax.servlet.http.HttpServletRequest; import java.util.Collections; -public class FhirResourceDaoCompositionR4 extends BaseHapiFhirResourceDao implements IFhirResourceDaoComposition { +public class JpaResourceDaoComposition extends BaseHapiFhirResourceDao implements IFhirResourceDaoComposition { @Override public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, IPrimitiveType theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoObservation.java similarity index 73% rename from hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java rename to hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoObservation.java index 77d459c57a6..c1c767fcad1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDaoObservation.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoObservation.java @@ -28,30 +28,71 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.api.CacheControlDirective; +import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.SortOrderEnum; import ca.uhn.fhir.rest.api.SortSpec; +import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import ca.uhn.fhir.rest.param.ReferenceOrListParam; import ca.uhn.fhir.rest.param.ReferenceParam; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Observation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.support.TransactionTemplate; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TreeMap; -public abstract class BaseHapiFhirResourceDaoObservation extends BaseHapiFhirResourceDao implements IFhirResourceDaoObservation { +public class JpaResourceDaoObservation extends BaseHapiFhirResourceDao implements IFhirResourceDaoObservation { + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; @Autowired ObservationLastNIndexPersistSvc myObservationLastNIndexPersistSvc; - @Autowired private IRequestPartitionHelperSvc myRequestPartitionHelperService; + @Override + public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { + updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); + + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); + return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); + } + + private String getEffectiveParamName() { + return Observation.SP_DATE; + } + + private String getCodeParamName() { + return Observation.SP_CODE; + } + + private String getSubjectParamName() { + return Observation.SP_SUBJECT; + } + + private String getPatientParamName() { + return Observation.SP_PATIENT; + } + + @Override + public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, + boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { + return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, + thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, + theCreateNewHistoryEntry); + } + protected ResourceTable updateObservationEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { @@ -130,12 +171,4 @@ public abstract class BaseHapiFhirResourceDaoObservation implements IFhirResourceDaoSearchParameter { +public class JpaResourceDaoSearchParameter extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { private static final Pattern REGEX_SP_EXPRESSION_HAS_PATH = Pattern.compile("[( ]*([A-Z][a-zA-Z]+\\.)?[a-z].*"); @Autowired - private ISearchParamExtractor mySearchParamExtractor; + private VersionCanonicalizer myVersionCanonicalizer; - protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) { + protected void reindexAffectedResources(T theResource, RequestDetails theRequestDetails) { + // N.B. Don't do this on the canonicalized version Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; - String expression = theResource != null ? theResource.getExpression() : null; - List base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null; + + org.hl7.fhir.r5.model.SearchParameter searchParameter = myVersionCanonicalizer.searchParameterToCanonical(theResource); + List base = theResource != null ? searchParameter.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null; requestReindexForRelatedResources(reindex, base, theRequestDetails); } @Override - protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { + protected void postPersist(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) { super.postPersist(theEntity, theResource, theRequestDetails); reindexAffectedResources(theResource, theRequestDetails); } @Override - protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { + protected void postUpdate(ResourceTable theEntity, T theResource, RequestDetails theRequestDetails) { super.postUpdate(theEntity, theResource, theRequestDetails); reindexAffectedResources(theResource, theRequestDetails); } @Override - protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { + protected void preDelete(T theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); reindexAffectedResources(theResourceToDelete, theRequestDetails); } @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { + protected void validateResourceForStorage(T theResource, ResourceTable theEntityToSave) { super.validateResourceForStorage(theResource, theEntityToSave); - validateSearchParam(theResource, getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor); + validateSearchParam(theResource, getContext(), getConfig()); } - public static void validateSearchParam(SearchParameter theResource, FhirContext theContext, DaoConfig theDaoConfig, ISearchParamRegistry theSearchParamRegistry, ISearchParamExtractor theSearchParamExtractor) { + public void validateSearchParam(IBaseResource theResource, FhirContext theContext, DaoConfig theDaoConfig) { + org.hl7.fhir.r5.model.SearchParameter searchParameter = myVersionCanonicalizer.searchParameterToCanonical(theResource); /* * If overriding built-in SPs is disabled on this server, make sure we aren't * doing that */ if (theDaoConfig.getModelConfig().isDefaultSearchParamsCanBeOverridden() == false) { - for (IPrimitiveType nextBaseType : theResource.getBase()) { + for (IPrimitiveType nextBaseType : searchParameter.getBase()) { String nextBase = nextBaseType.getValueAsString(); - RuntimeSearchParam existingSearchParam = theSearchParamRegistry.getActiveSearchParam(nextBase, theResource.getCode()); + RuntimeSearchParam existingSearchParam = mySearchParamRegistry.getActiveSearchParam(nextBase, searchParameter.getCode()); if (existingSearchParam != null) { boolean isBuiltIn = existingSearchParam.getId() == null; isBuiltIn |= existingSearchParam.getUri().startsWith("http://hl7.org/fhir/SearchParameter/"); if (isBuiltIn) { - throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + theResource.getCode() + " because overriding is disabled on this server"); + throw new UnprocessableEntityException(Msg.code(1111) + "Can not override built-in search parameter " + nextBase + ":" + searchParameter.getCode() + " because overriding is disabled on this server"); } } } @@ -109,34 +112,34 @@ public class FhirResourceDaoSearchParameterR4 extends BaseHapiFhirResourceDao theValueAsString.equals(t.getValueAsPrimitive().getValueAsString())); + .anyMatch(t -> theValueAsString.equals(t.getValueAsPrimitive().getValueAsString())); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCompositionDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCompositionDstu3.java deleted file mode 100644 index a6d55f0b760..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoCompositionDstu3.java +++ /dev/null @@ -1,59 +0,0 @@ -package ca.uhn.fhir.jpa.dao.dstu3; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.dstu3.model.Composition; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collections; - -public class FhirResourceDaoCompositionDstu3 extends BaseHapiFhirResourceDao implements IFhirResourceDaoComposition { - - @Override - public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, IPrimitiveType theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) { - SearchParameterMap paramMap = new SearchParameterMap(); - if (theCount != null) { - paramMap.setCount(theCount.getValue()); - } - if (theOffset != null) { - paramMap.setOffset(theOffset.getValue()); - } - paramMap.setIncludes(Collections.singleton(IResource.INCLUDE_ALL.asRecursive())); - paramMap.setSort(theSort); - paramMap.setLastUpdated(theLastUpdate); - if (theId != null) { - paramMap.add("_id", new StringParam(theId.getIdPart())); - } - return search(paramMap, theRequestDetails); - } -} - diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java deleted file mode 100644 index cc2cb6b0d0b..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoObservationDstu3.java +++ /dev/null @@ -1,83 +0,0 @@ -package ca.uhn.fhir.jpa.dao.dstu3; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import org.hl7.fhir.dstu3.model.Observation; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -public class FhirResourceDaoObservationDstu3 extends BaseHapiFhirResourceDaoObservation { - - @Autowired - private IRequestPartitionHelperSvc myPartitionHelperSvc; - - @Override - public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { - - updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); - return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); - } - - @Override - protected String getEffectiveParamName() { - return Observation.SP_DATE; - } - - @Override - protected String getCodeParamName() { - return Observation.SP_CODE; - } - - @Override - protected String getSubjectParamName() { - return Observation.SP_SUBJECT; - } - - @Override - protected String getPatientParamName() { - return Observation.SP_PATIENT; - } - - @Override - public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, - boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { - return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, - thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, - theCreateNewHistoryEntry); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java deleted file mode 100644 index c8109c78936..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoSearchParameterDstu3.java +++ /dev/null @@ -1,80 +0,0 @@ -package ca.uhn.fhir.jpa.dao.dstu3; - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_30_40; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_40; -import org.hl7.fhir.dstu3.model.PrimitiveType; -import org.hl7.fhir.dstu3.model.SearchParameter; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.stream.Collectors; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -public class FhirResourceDaoSearchParameterDstu3 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - - @Autowired - private ISearchParamExtractor mySearchParamExtractor; - - protected void reindexAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) { - Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; - String expression = theResource != null ? theResource.getExpression() : null; - List base = theResource != null ? theResource.getBase().stream().map(PrimitiveType::asStringValue).collect(Collectors.toList()) : null; - requestReindexForRelatedResources(reindex, base, theRequestDetails); - } - - - @Override - protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postPersist(theEntity, theResource, theRequestDetails); - reindexAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postUpdate(theEntity, theResource, theRequestDetails); - reindexAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { - super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); - reindexAffectedResources(theResourceToDelete, theRequestDetails); - } - - @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { - super.validateResourceForStorage(theResource, theEntityToSave); - - org.hl7.fhir.r4.model.SearchParameter resource = (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_30_40.convertResource(theResource, new BaseAdvisor_30_40(false)); - - FhirResourceDaoSearchParameterR4.validateSearchParam( - resource, - getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java deleted file mode 100644 index f6807ffbc8b..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoObservationR4.java +++ /dev/null @@ -1,88 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4.model.Observation; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.PersistenceContextType; -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -public class FhirResourceDaoObservationR4 extends BaseHapiFhirResourceDaoObservation { - - - @PersistenceContext(type = PersistenceContextType.TRANSACTION) - protected EntityManager myEntityManager; - @Autowired - private IRequestPartitionHelperSvc myRequestPartitionHelperService; - - @Override - public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { - updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - - RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); - return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); - } - - @Override - protected String getEffectiveParamName() { - return Observation.SP_DATE; - } - - @Override - protected String getCodeParamName() { - return Observation.SP_CODE; - } - - @Override - protected String getSubjectParamName() { - return Observation.SP_SUBJECT; - } - - @Override - protected String getPatientParamName() { - return Observation.SP_PATIENT; - } - - @Override - public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, - boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { - return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, - thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, - theCreateNewHistoryEntry); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoCompositionR4B.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoCompositionR4B.java deleted file mode 100644 index 6fccd75ec9a..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoCompositionR4B.java +++ /dev/null @@ -1,59 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4b; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r4b.model.Composition; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collections; - -public class FhirResourceDaoCompositionR4B extends BaseHapiFhirResourceDao implements IFhirResourceDaoComposition { - - @Override - public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, IPrimitiveType theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) { - SearchParameterMap paramMap = new SearchParameterMap(); - if (theCount != null) { - paramMap.setCount(theCount.getValue()); - } - if (theOffset != null) { - paramMap.setOffset(theOffset.getValue()); - } - paramMap.setIncludes(Collections.singleton(IBaseResource.INCLUDE_ALL.asRecursive())); - paramMap.setSort(theSort); - paramMap.setLastUpdated(theLastUpdate); - if (theId != null) { - paramMap.add("_id", new StringParam(theId.getIdPart())); - } - return search(paramMap, theRequestDetails); - } -} - diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoObservationR4B.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoObservationR4B.java deleted file mode 100644 index 9ed236f9b63..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoObservationR4B.java +++ /dev/null @@ -1,83 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4b; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r4b.model.Observation; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -public class FhirResourceDaoObservationR4B extends BaseHapiFhirResourceDaoObservation { - - @Autowired - private IRequestPartitionHelperSvc myPartitionHelperSvc; - - @Override - public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { - - updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); - return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); - } - - @Override - protected String getEffectiveParamName() { - return org.hl7.fhir.r4.model.Observation.SP_DATE; - } - - @Override - protected String getCodeParamName() { - return org.hl7.fhir.r4.model.Observation.SP_CODE; - } - - @Override - protected String getSubjectParamName() { - return org.hl7.fhir.r4.model.Observation.SP_SUBJECT; - } - - @Override - protected String getPatientParamName() { - return org.hl7.fhir.r4.model.Observation.SP_PATIENT; - } - - @Override - public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, - boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { - return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, - thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, - theCreateNewHistoryEntry); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoSearchParameterR4B.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoSearchParameterR4B.java deleted file mode 100644 index b905f1d8738..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4b/FhirResourceDaoSearchParameterR4B.java +++ /dev/null @@ -1,83 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r4b; - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; -import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_43_50; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50; -import org.hl7.fhir.r4b.model.CodeType; -import org.hl7.fhir.r4b.model.SearchParameter; -import org.hl7.fhir.r5.model.Resource; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.stream.Collectors; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -public class FhirResourceDaoSearchParameterR4B extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - - @Autowired - private ISearchParamExtractor mySearchParamExtractor; - - protected void refactorAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) { - Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; - String expression = theResource != null ? theResource.getExpression() : null; - List base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null; - requestReindexForRelatedResources(reindex, base, theRequestDetails); - } - - - @Override - protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postPersist(theEntity, theResource, theRequestDetails); - refactorAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postUpdate(theEntity, theResource, theRequestDetails); - refactorAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { - super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); - refactorAffectedResources(theResourceToDelete, theRequestDetails); - } - - @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { - super.validateResourceForStorage(theResource, theEntityToSave); - - Resource resR5 = VersionConvertorFactory_43_50.convertResource(theResource, new BaseAdvisor_43_50(false)); - FhirResourceDaoSearchParameterR4.validateSearchParam( - (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_40_50.convertResource(resR5, new BaseAdvisor_40_50(false)), - getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor); - } - - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCompositionR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCompositionR5.java deleted file mode 100644 index 1b242b8250e..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoCompositionR5.java +++ /dev/null @@ -1,59 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r5; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoComposition; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.SortSpec; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.param.DateRangeParam; -import ca.uhn.fhir.rest.param.StringParam; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IIdType; -import org.hl7.fhir.instance.model.api.IPrimitiveType; -import org.hl7.fhir.r5.model.Composition; - -import javax.servlet.http.HttpServletRequest; -import java.util.Collections; - -public class FhirResourceDaoCompositionR5 extends BaseHapiFhirResourceDao implements IFhirResourceDaoComposition { - - @Override - public IBundleProvider getDocumentForComposition(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType theCount, IPrimitiveType theOffset, DateRangeParam theLastUpdate, SortSpec theSort, RequestDetails theRequestDetails) { - SearchParameterMap paramMap = new SearchParameterMap(); - if (theCount != null) { - paramMap.setCount(theCount.getValue()); - } - if (theOffset != null) { - paramMap.setOffset(theOffset.getValue()); - } - paramMap.setIncludes(Collections.singleton(IBaseResource.INCLUDE_ALL.asRecursive())); - paramMap.setSort(theSort); - paramMap.setLastUpdated(theLastUpdate); - if (theId != null) { - paramMap.add("_id", new StringParam(theId.getIdPart())); - } - return search(paramMap, theRequestDetails); - } -} - diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java deleted file mode 100644 index e6855cd6b50..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoObservationR5.java +++ /dev/null @@ -1,83 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r5; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDaoObservation; -import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; -import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; -import ca.uhn.fhir.rest.api.CacheControlDirective; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.server.IBundleProvider; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.r5.model.Observation; -import org.springframework.beans.factory.annotation.Autowired; - -import javax.servlet.http.HttpServletResponse; -import java.util.Date; - -public class FhirResourceDaoObservationR5 extends BaseHapiFhirResourceDaoObservation { - - @Autowired - private IRequestPartitionHelperSvc myPartitionHelperSvc; - - @Override - public IBundleProvider observationsLastN(SearchParameterMap theSearchParameterMap, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) { - - updateSearchParamsForLastn(theSearchParameterMap, theRequestDetails); - - RequestPartitionId requestPartitionId = myPartitionHelperSvc.determineReadPartitionForRequestForSearchType(theRequestDetails, getResourceName(), theSearchParameterMap, null); - return mySearchCoordinatorSvc.registerSearch(this, theSearchParameterMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)), theRequestDetails, requestPartitionId); - } - - @Override - protected String getEffectiveParamName() { - return org.hl7.fhir.r4.model.Observation.SP_DATE; - } - - @Override - protected String getCodeParamName() { - return org.hl7.fhir.r4.model.Observation.SP_CODE; - } - - @Override - protected String getSubjectParamName() { - return org.hl7.fhir.r4.model.Observation.SP_SUBJECT; - } - - @Override - protected String getPatientParamName() { - return org.hl7.fhir.r4.model.Observation.SP_PATIENT; - } - - @Override - public ResourceTable updateEntity(RequestDetails theRequest, IBaseResource theResource, IBasePersistedResource theEntity, Date theDeletedTimestampOrNull, boolean thePerformIndexing, - boolean theUpdateVersion, TransactionDetails theTransactionDetails, boolean theForceUpdate, boolean theCreateNewHistoryEntry) { - return updateObservationEntity(theRequest, theResource, theEntity, theDeletedTimestampOrNull, - thePerformIndexing, theUpdateVersion, theTransactionDetails, theForceUpdate, - theCreateNewHistoryEntry); - } - -} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java deleted file mode 100644 index 0f3a4481357..00000000000 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r5/FhirResourceDaoSearchParameterR5.java +++ /dev/null @@ -1,79 +0,0 @@ -package ca.uhn.fhir.jpa.dao.r5; - -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoSearchParameter; -import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao; -import ca.uhn.fhir.jpa.dao.r4.FhirResourceDaoSearchParameterR4; -import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor; -import ca.uhn.fhir.rest.api.server.RequestDetails; -import org.hl7.fhir.convertors.advisors.impl.BaseAdvisor_40_50; -import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; -import org.hl7.fhir.r5.model.CodeType; -import org.hl7.fhir.r5.model.SearchParameter; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.List; -import java.util.stream.Collectors; - -/* - * #%L - * HAPI FHIR JPA Server - * %% - * Copyright (C) 2014 - 2022 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% - */ - -public class FhirResourceDaoSearchParameterR5 extends BaseHapiFhirResourceDao implements IFhirResourceDaoSearchParameter { - - @Autowired - private ISearchParamExtractor mySearchParamExtractor; - - protected void refactorAffectedResources(SearchParameter theResource, RequestDetails theRequestDetails) { - Boolean reindex = theResource != null ? CURRENTLY_REINDEXING.get(theResource) : null; - String expression = theResource != null ? theResource.getExpression() : null; - List base = theResource != null ? theResource.getBase().stream().map(CodeType::getCode).collect(Collectors.toList()) : null; - requestReindexForRelatedResources(reindex, base, theRequestDetails); - } - - - @Override - protected void postPersist(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postPersist(theEntity, theResource, theRequestDetails); - refactorAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void postUpdate(ResourceTable theEntity, SearchParameter theResource, RequestDetails theRequestDetails) { - super.postUpdate(theEntity, theResource, theRequestDetails); - refactorAffectedResources(theResource, theRequestDetails); - } - - @Override - protected void preDelete(SearchParameter theResourceToDelete, ResourceTable theEntityToDelete, RequestDetails theRequestDetails) { - super.preDelete(theResourceToDelete, theEntityToDelete, theRequestDetails); - refactorAffectedResources(theResourceToDelete, theRequestDetails); - } - - @Override - protected void validateResourceForStorage(SearchParameter theResource, ResourceTable theEntityToSave) { - super.validateResourceForStorage(theResource, theEntityToSave); - - FhirResourceDaoSearchParameterR4.validateSearchParam( - (org.hl7.fhir.r4.model.SearchParameter) VersionConvertorFactory_40_50.convertResource(theResource, new BaseAdvisor_40_50(false)), - getContext(), getConfig(), mySearchParamRegistry, mySearchParamExtractor); - } - - -} diff --git a/hapi-fhir-jpaserver-cql/pom.xml b/hapi-fhir-jpaserver-cql/pom.xml index b104e7f84a4..1e743702eef 100644 --- a/hapi-fhir-jpaserver-cql/pom.xml +++ b/hapi-fhir-jpaserver-cql/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/BaseCqlR4Test.java b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/BaseCqlR4Test.java index 93c071b7bf6..148000bea00 100644 --- a/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/BaseCqlR4Test.java +++ b/hapi-fhir-jpaserver-cql/src/test/java/ca/uhn/fhir/cql/BaseCqlR4Test.java @@ -44,12 +44,6 @@ public class BaseCqlR4Test extends BaseJpaR4Test implements CqlProviderTestBase @RegisterExtension protected PartitionHelper myPartitionHelper; - // FIXME: restore? -// @Override -// public void beforeResetInterceptors() { -// myInterceptorRegistry.unregisterInterceptorsIf(t->!(t instanceof PartitionHelper.MyTestInterceptor)); -// } - @Autowired protected DaoRegistry myDaoRegistry; diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml index d301eadd9c1..87ef97cb206 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index 0155cdfef91..f1f6bfd85f4 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 81b2a142592..61b7e9e962c 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 08ee6fb1b56..803a246028b 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java index d614162a5f7..9b17e9ba17b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java @@ -179,33 +179,35 @@ public class SearchParameterCanonicalizer { String path = theNextSp.getExpression(); RestSearchParameterTypeEnum paramType = null; RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case NULL: - break; + if (theNextSp.getType() != null) { + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case NULL: + break; + } } if (theNextSp.getStatus() != null) { switch (theNextSp.getStatus()) { diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 2210b8808f3..9d201a1d717 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java index 4cd455382c7..e9c780aa53f 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/submit/interceptor/SubscriptionValidatingInterceptor.java @@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.subscription.model.CanonicalSubscriptionChannelType; import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.HapiExtensions; @@ -94,7 +95,12 @@ public class SubscriptionValidatingInterceptor { return; } - CanonicalSubscription subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription); + CanonicalSubscription subscription; + try { + subscription = mySubscriptionCanonicalizer.canonicalize(theSubscription); + } catch (InternalErrorException e) { + throw new UnprocessableEntityException(Msg.code(955) + e.getMessage()); + } boolean finished = false; if (subscription.getStatus() == null) { throw new UnprocessableEntityException(Msg.code(8) + "Can not process submitted Subscription - Subscription.status must be populated on this server"); diff --git a/hapi-fhir-jpaserver-test-dstu2/pom.xml b/hapi-fhir-jpaserver-test-dstu2/pom.xml index 4cbf912c4a2..853e2b8dc34 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java index 9e110f92610..0dcef38db22 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/dao/dstu2/BaseJpaDstu2Test.java @@ -22,6 +22,7 @@ import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistryController; import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionLoader; import ca.uhn.fhir.jpa.test.BaseJpaTest; +import ca.uhn.fhir.jpa.test.PreventDanglingInterceptorsExtension; import ca.uhn.fhir.jpa.test.config.TestDstu2Config; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.model.dstu2.composite.MetaDt; @@ -60,6 +61,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; @@ -213,6 +215,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { @Autowired private ValidationSupportChain myJpaValidationSupportChain; + @RegisterExtension + private final PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry); + @BeforeEach public void beforeFlushFT() { purgeHibernateSearch(myEntityManager); @@ -232,13 +237,6 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest { myDaoConfig.setAllowExternalReferences(new DaoConfig().isAllowExternalReferences()); } - @Override - @AfterEach - public void afterResetInterceptors() { - super.afterResetInterceptors(); - myInterceptorRegistry.unregisterAllInterceptors(); - } - @Override public FhirContext getFhirContext() { return myFhirContext; diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java index fdef4afdc48..dfa36888c9c 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/SubscriptionsDstu2Test.java @@ -58,6 +58,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { Subscription subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setEndpoint("http://localhost"); try { myClient.create().resource(subs).execute(); fail(); @@ -105,6 +106,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { public void testCreateWithPopulatedButInvalidStatue() { Subscription subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelTypeEnum.WEBSOCKET); + subs.getChannel().setEndpoint("http://localhost"); subs.setCriteria("Observation?identifier=123"); subs.getStatusElement().setValue("aaaaa"); @@ -112,7 +114,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { myClient.create().resource(subs).execute(); fail(); } catch (UnprocessableEntityException e) { - assertThat(e.getMessage(), containsString("Subscription.status must be populated on this server")); + assertThat(e.getMessage(), containsString("Unknown SubscriptionStatus code 'aaaaa'")); } } @@ -152,6 +154,7 @@ public class SubscriptionsDstu2Test extends BaseResourceProviderDstu2Test { Subscription subs = new Subscription(); subs.getChannel().setType(SubscriptionChannelTypeEnum.REST_HOOK); subs.setCriteria("Observation?identifier=123"); + subs.getChannel().setEndpoint("http://localhost"); subs.setStatus(SubscriptionStatusEnum.REQUESTED); IIdType id = myClient.create().resource(subs).execute().getId(); subs.setId(id); diff --git a/hapi-fhir-jpaserver-test-dstu3/pom.xml b/hapi-fhir-jpaserver-test-dstu3/pom.xml index 434f88fa1d5..5977ad827b6 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java index 46df7dd8150..eac5011dc5d 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3SearchCustomSearchParamTest.java @@ -93,7 +93,7 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu } @Test - public void testCreateSpWithMultiplePaths(){ + public void testCreateSpWithMultiplePaths() { SearchParameter sp = new SearchParameter(); sp.setCode("telephone-unformatted"); sp.setStatus(Enumerations.PublicationStatus.ACTIVE); @@ -101,18 +101,56 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu sp.getBase().add(new CodeType("Patient")); sp.setType(Enumerations.SearchParamType.TOKEN); - DaoMethodOutcome daoMethodOutcome = mySearchParameterDao.create(sp); + DaoMethodOutcome daoMethodOutcome; + + daoMethodOutcome = mySearchParameterDao.create(sp); assertThat(daoMethodOutcome.getId(), is(notNullValue())); + } + + @Test + public void testCreateSpWithMultiplePaths2() { + SearchParameter sp = new SearchParameter(); + sp.setCode("telephone-unformatted"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setExpression("Patient.telecom.where(system='phone' or system='email')"); + sp.getBase().add(new CodeType("Patient")); + sp.setType(Enumerations.SearchParamType.TOKEN); + + DaoMethodOutcome daoMethodOutcome; sp.setExpression("Patient.telecom.where(system='phone') or Patient.telecome.where(system='email')"); sp.setCode("telephone-unformatted-2"); daoMethodOutcome = mySearchParameterDao.create(sp); assertThat(daoMethodOutcome.getId(), is(notNullValue())); + } + + @Test + public void testCreateSpWithMultiplePaths3() { + SearchParameter sp = new SearchParameter(); + sp.setCode("telephone-unformatted"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setExpression("Patient.telecom.where(system='phone' or system='email')"); + sp.getBase().add(new CodeType("Patient")); + sp.setType(Enumerations.SearchParamType.TOKEN); + + DaoMethodOutcome daoMethodOutcome; sp.setExpression("Patient.telecom.where(system='phone' or system='email') | Patient.telecome.where(system='email')"); sp.setCode("telephone-unformatted-3"); daoMethodOutcome = mySearchParameterDao.create(sp); assertThat(daoMethodOutcome.getId(), is(notNullValue())); + } + + @Test + public void testCreateSpWithMultiplePaths4() { + SearchParameter sp = new SearchParameter(); + sp.setCode("telephone-unformatted"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setExpression("Patient.telecom.where(system='phone' or system='email')"); + sp.getBase().add(new CodeType("Patient")); + sp.setType(Enumerations.SearchParamType.TOKEN); + + DaoMethodOutcome daoMethodOutcome; sp.setExpression("Patient.telecom.where(system='phone' or system='email') | Patient.telecom.where(system='email') or Patient.telecom.where(system='mail' | system='phone')"); sp.setCode("telephone-unformatted-3"); @@ -120,7 +158,6 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu assertThat(daoMethodOutcome.getId(), is(notNullValue())); } - @Test public void testCreateInvalidParamNoPath() { SearchParameter fooSp = new SearchParameter(); @@ -1110,21 +1147,21 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu medAdmin.setMedication(new Reference(medication)); myMedicationAdministrationDao.create(medAdmin); - - runInTransaction(()->{ + + runInTransaction(() -> { List tokens = myResourceIndexedSearchParamTokenDao .findAll() .stream() .filter(t -> t.getParamName().equals("medicationadministration-ingredient-medication")) .collect(Collectors.toList()); - ourLog.info("Tokens:\n * {}", tokens.stream().map(t->t.toString()).collect(Collectors.joining("\n * "))); + ourLog.info("Tokens:\n * {}", tokens.stream().map(t -> t.toString()).collect(Collectors.joining("\n * "))); assertEquals(1, tokens.size(), tokens.toString()); assertEquals(false, tokens.get(0).isMissing()); }); SearchParameterMap map = SearchParameterMap.newSynchronous(); - map.add("medicationadministration-ingredient-medication", new TokenParam("system","code")); + map.add("medicationadministration-ingredient-medication", new TokenParam("system", "code")); myCaptureQueriesListener.clear(); IBundleProvider search = myMedicationAdministrationDao.search(map); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); @@ -1133,5 +1170,4 @@ public class FhirResourceDaoDstu3SearchCustomSearchParamTest extends BaseJpaDstu } - } diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 6bd5d059b23..6dfc992e56a 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/Batch2CoordinatorIT.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/Batch2CoordinatorIT.java index 1ae42a58384..976c633973e 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/Batch2CoordinatorIT.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/batch2/Batch2CoordinatorIT.java @@ -404,18 +404,6 @@ public class Batch2CoordinatorIT extends BaseJpaR4Test { @Test public void testStepRunFailure_continuouslyThrows_marksJobFailed() { - // FIXME: remove -// AtomicInteger interceptorCounter = new AtomicInteger(); -// myWorkChannel.addInterceptor(new ExecutorChannelInterceptor() { -// @Override -// public void afterMessageHandled(Message message, MessageChannel channel, MessageHandler handler, Exception ex) { -// if (ex != null) { -// interceptorCounter.incrementAndGet(); -// ourLog.info("Work Channel Exception thrown: {}. Resending message", ex.getMessage()); -// channel.send(message); -// } -// } -// }); // setup AtomicInteger counter = new AtomicInteger(); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java similarity index 82% rename from hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java rename to hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java index 79ae7d6318c..c0e0b50861c 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoSearchParameterR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jpa.dao.r4; +package ca.uhn.fhir.jpa.dao; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; @@ -6,6 +6,7 @@ import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.hapi.converters.canonical.VersionCanonicalizer; import org.hl7.fhir.r4.model.Enumerations; import org.hl7.fhir.r4.model.SearchParameter; import org.junit.jupiter.api.BeforeEach; @@ -22,11 +23,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @ExtendWith(MockitoExtension.class) -public class FhirResourceDaoSearchParameterR4Test { +public class JpaResourceDaoSearchParameterTest { - private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoSearchParameterR4Test.class); + private static final Logger ourLog = LoggerFactory.getLogger(JpaResourceDaoSearchParameterTest.class); private FhirContext myCtx; - private FhirResourceDaoSearchParameterR4 myDao; + private JpaResourceDaoSearchParameter myDao; @Mock private ApplicationContext myApplicationContext; @@ -34,10 +35,13 @@ public class FhirResourceDaoSearchParameterR4Test { public void before() { myCtx = FhirContext.forR4Cached(); - myDao = new FhirResourceDaoSearchParameterR4(); + VersionCanonicalizer versionCanonicalizer = new VersionCanonicalizer(myCtx); + + myDao = new JpaResourceDaoSearchParameter<>(); myDao.setContext(myCtx); myDao.setDaoConfigForUnitTest(new DaoConfig()); myDao.setApplicationContext(myApplicationContext); + myDao.setVersionCanonicalizerForUnitTest(versionCanonicalizer); myDao.start(); } @@ -58,7 +62,7 @@ public class FhirResourceDaoSearchParameterR4Test { nextSearchParameter.setExpression(nextp.getPath()); nextSearchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); nextSearchParameter.setType(Enumerations.SearchParamType.fromCode(nextp.getParamType().getCode())); - nextp.getBase().forEach(t -> nextSearchParameter.addBase(t)); + nextp.getBase().forEach(nextSearchParameter::addBase); ourLog.info("Validating {}.{}", nextResource, nextp.getName()); myDao.validateResourceForStorage(nextSearchParameter, null); diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index f6aa32ae643..77e75be98f6 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4b/src/test/java/ca/uhn/fhir/jpa/dao/r4b/BaseJpaR4BTest.java b/hapi-fhir-jpaserver-test-r4b/src/test/java/ca/uhn/fhir/jpa/dao/r4b/BaseJpaR4BTest.java index 2641e00821f..b4df3ab85e0 100644 --- a/hapi-fhir-jpaserver-test-r4b/src/test/java/ca/uhn/fhir/jpa/dao/r4b/BaseJpaR4BTest.java +++ b/hapi-fhir-jpaserver-test-r4b/src/test/java/ca/uhn/fhir/jpa/dao/r4b/BaseJpaR4BTest.java @@ -54,6 +54,7 @@ import ca.uhn.fhir.jpa.subscription.match.registry.SubscriptionRegistry; import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.test.BaseJpaTest; +import ca.uhn.fhir.jpa.test.PreventDanglingInterceptorsExtension; import ca.uhn.fhir.jpa.test.config.TestR4BConfig; import ca.uhn.fhir.jpa.util.ResourceCountCache; import ca.uhn.fhir.parser.IParser; @@ -117,6 +118,7 @@ import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; @@ -382,6 +384,7 @@ public abstract class BaseJpaR4BTest extends BaseJpaTest implements ITestDataBui @Autowired private IBulkDataExportJobSchedulingHelper myBulkDataSchedulerHelper; + @Override public IIdType doCreateResource(IBaseResource theResource) { IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass()); @@ -412,9 +415,11 @@ public abstract class BaseJpaR4BTest extends BaseJpaTest implements ITestDataBui myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE); } + @Override @AfterEach public void afterResetInterceptors() { - myInterceptorRegistry.unregisterAllInterceptors(); + super.afterResetInterceptors(); + myInterceptorRegistry.unregisterInterceptor(myPerformanceTracingLoggingInterceptor); } @AfterEach() diff --git a/hapi-fhir-jpaserver-test-r5/pom.xml b/hapi-fhir-jpaserver-test-r5/pom.xml index 3d965df565e..d0c8c2d8971 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java index b304c94ea56..7dd5284b0c7 100644 --- a/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java +++ b/hapi-fhir-jpaserver-test-r5/src/test/java/ca/uhn/fhir/jpa/dao/r5/BaseJpaR5Test.java @@ -60,20 +60,14 @@ import ca.uhn.fhir.jpa.term.api.ITermCodeSystemStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermDeferredStorageSvc; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.jpa.test.BaseJpaTest; +import ca.uhn.fhir.jpa.test.PreventDanglingInterceptorsExtension; import ca.uhn.fhir.jpa.test.config.TestR5Config; import ca.uhn.fhir.jpa.util.ResourceCountCache; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.StrictErrorHandler; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.server.BasePagingProvider; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory; import ca.uhn.fhir.test.utilities.ITestDataBuilder; -import ca.uhn.fhir.util.UrlUtil; -import ca.uhn.fhir.validation.FhirValidator; -import ca.uhn.fhir.validation.ValidationResult; -import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r5.model.AllergyIntolerance; @@ -89,16 +83,12 @@ import org.hl7.fhir.r5.model.Communication; import org.hl7.fhir.r5.model.CommunicationRequest; import org.hl7.fhir.r5.model.CompartmentDefinition; import org.hl7.fhir.r5.model.ConceptMap; -import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; -import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; -import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; import org.hl7.fhir.r5.model.Condition; import org.hl7.fhir.r5.model.Consent; import org.hl7.fhir.r5.model.Coverage; import org.hl7.fhir.r5.model.Device; import org.hl7.fhir.r5.model.DiagnosticReport; import org.hl7.fhir.r5.model.Encounter; -import org.hl7.fhir.r5.model.Enumerations; import org.hl7.fhir.r5.model.Group; import org.hl7.fhir.r5.model.Immunization; import org.hl7.fhir.r5.model.ImmunizationRecommendation; @@ -126,12 +116,11 @@ import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.Subscription; import org.hl7.fhir.r5.model.Substance; import org.hl7.fhir.r5.model.Task; -import org.hl7.fhir.r5.model.UriType; import org.hl7.fhir.r5.model.ValueSet; -import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; @@ -142,22 +131,16 @@ import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; -import java.io.IOException; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; @ExtendWith(SpringExtension.class) @ContextConfiguration(classes = {TestR5Config.class}) public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuilder { - private static IValidationSupport ourJpaValidationSupportChainR5; - private static IFhirResourceDaoValueSet ourValueSetDao; @Autowired protected ITermCodeSystemStorageSvc myTermCodeSystemStorageSvc; @Autowired @@ -401,8 +384,10 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil protected ITermDeferredStorageSvc myTermDeferredStorageSvc; @Autowired private IValidationSupport myJpaValidationSupportChain; + @RegisterExtension + private PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(() -> myInterceptorRegistry); + private PerformanceTracingLoggingInterceptor myPerformanceTracingLoggingInterceptor; - private List mySystemInterceptors; @Autowired private DaoRegistry myDaoRegistry; @Autowired @@ -438,13 +423,6 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil myPagingProvider.setMaximumPageSize(BasePagingProvider.DEFAULT_MAX_PAGE_SIZE); } - @Override - @AfterEach - public void afterResetInterceptors() { - super.afterResetInterceptors(); - myInterceptorRegistry.unregisterAllInterceptors(); - } - @AfterEach public void afterClearTerminologyCaches() { TermReadSvcImpl baseHapiTerminologySvc = AopTestUtils.getTargetObject(myTermSvc); @@ -455,16 +433,15 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil deferredStorageSvc.clearDeferred(); } - @AfterEach() - public void afterGrabCaches() { - ourValueSetDao = myValueSetDao; - ourJpaValidationSupportChainR5 = myJpaValidationSupportChain; + @AfterEach + @Override + protected void afterResetInterceptors() { + super.afterResetInterceptors(); + myInterceptorRegistry.unregisterInterceptor(myPerformanceTracingLoggingInterceptor); } @BeforeEach public void beforeCreateInterceptor() { - mySystemInterceptors = myInterceptorRegistry.getAllRegisteredInterceptors(); - myInterceptor = mock(IServerInterceptor.class); myPerformanceTracingLoggingInterceptor = new PerformanceTracingLoggingInterceptor(); @@ -495,60 +472,13 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil return myTxManager; } - protected void validate(IBaseResource theResource) { - FhirValidator validatorModule = myFhirCtx.newValidator(); - FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myValidationSupport); - instanceValidator.setBestPracticeWarningLevel(BestPracticeWarningLevel.Ignore); - validatorModule.registerValidatorModule(instanceValidator); - ValidationResult result = validatorModule.validateWithResult(theResource); - if (!result.isSuccessful()) { - fail(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome())); - } - } - - @SuppressWarnings("unchecked") - protected void upload(String theClasspath) throws IOException { - String resource = loadResource(theClasspath); - IParser parser = EncodingEnum.detectEncoding(resource).newParser(myFhirCtx); - IBaseResource resourceParsed = parser.parseResource(resource); - IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceParsed.getIdElement().getResourceType()); - dao.update(resourceParsed); - } - - protected ValueSet.ValueSetExpansionContainsComponent assertExpandedValueSetContainsConcept(ValueSet theValueSet, String theSystem, String theCode, String theDisplay, Integer theDesignationCount) { - List contains = theValueSet.getExpansion().getContains(); - - Stream stream = contains.stream(); - if (theSystem != null) { - stream = stream.filter(concept -> theSystem.equalsIgnoreCase(concept.getSystem())); - } - if (theCode != null) { - stream = stream.filter(concept -> theCode.equalsIgnoreCase(concept.getCode())); - } - if (theDisplay != null) { - stream = stream.filter(concept -> theDisplay.equalsIgnoreCase(concept.getDisplay())); - } - if (theDesignationCount != null) { - stream = stream.filter(concept -> concept.getDesignation().size() == theDesignationCount); - } - - Optional first = stream.findFirst(); - if (!first.isPresent()) { - String failureMessage = String.format("Expanded ValueSet %s did not contain concept [%s|%s|%s] with [%d] designations", theValueSet.getId(), theSystem, theCode, theDisplay, theDesignationCount); - fail(failureMessage); - return null; - } else { - return first.get(); - } - } - public List getExpandedConceptsByValueSetUrl(String theValuesetUrl) { return runInTransaction(() -> { Optional valueSetOpt = myTermSvc.findCurrentTermValueSet(theValuesetUrl); assertTrue(valueSetOpt.isPresent()); TermValueSet valueSet = valueSetOpt.get(); List concepts = valueSet.getConcepts(); - return concepts.stream().map(concept -> concept.getCode()).collect(Collectors.toList()); + return concepts.stream().map(TermValueSetConcept::getCode).collect(Collectors.toList()); }); } @@ -557,103 +487,4 @@ public abstract class BaseJpaR5Test extends BaseJpaTest implements ITestDataBuil myJpaValidationSupportChain.invalidateCaches(); } - /** - * Creates a single {@link ConceptMap} entity that includes: - *
    - *
      - *
    • - * One group with two elements, each identifying one target apiece. - *
    • - *
    • - * One group with one element, identifying two targets. - *
    • - *
    • - * One group with one element, identifying a target that also appears - * in the first element of the first group. - *
    • - *
    - *
    - * The first two groups identify the same source code system and different target code systems. - *
    - * The first two groups also include an element with the same source code. - * - * @return A {@link ConceptMap} entity for testing. - */ - public static ConceptMap createConceptMap() { - ConceptMap conceptMap = new ConceptMap(); - conceptMap.setUrl(CM_URL); - - conceptMap.setSourceScope(new UriType(VS_URL)); - conceptMap.setTargetScope(new UriType(VS_URL_2)); - - ConceptMapGroupComponent group = conceptMap.addGroup(); - group.setSource(CS_URL + "|" + "Version 1"); - group.setTarget(CS_URL_2 + "|" + "Version 2"); - - SourceElementComponent element = group.addElement(); - element.setCode("12345"); - element.setDisplay("Source Code 12345"); - - TargetElementComponent target = element.addTarget(); - target.setCode("34567"); - target.setDisplay("Target Code 34567"); - target.setRelationship(Enumerations.ConceptMapRelationship.EQUIVALENT); - - element = group.addElement(); - element.setCode("23456"); - element.setDisplay("Source Code 23456"); - - target = element.addTarget(); - target.setCode("45678"); - target.setDisplay("Target Code 45678"); - target.setRelationship(Enumerations.ConceptMapRelationship.SOURCEISBROADERTHANTARGET); - - // Add a duplicate - target = element.addTarget(); - target.setCode("45678"); - target.setDisplay("Target Code 45678"); - target.setRelationship(Enumerations.ConceptMapRelationship.SOURCEISBROADERTHANTARGET); - - group = conceptMap.addGroup(); - group.setSource(CS_URL + "|" + "Version 3"); - group.setTarget(CS_URL_3 + "|" + "Version 4"); - - element = group.addElement(); - element.setCode("12345"); - element.setDisplay("Source Code 12345"); - - target = element.addTarget(); - target.setCode("56789"); - target.setDisplay("Target Code 56789"); - target.setRelationship(Enumerations.ConceptMapRelationship.EQUIVALENT); - - target = element.addTarget(); - target.setCode("67890"); - target.setDisplay("Target Code 67890"); - target.setRelationship(Enumerations.ConceptMapRelationship.SOURCEISBROADERTHANTARGET); - - group = conceptMap.addGroup(); - group.setSource(CS_URL_4 + "|" + "Version 5"); - group.setTarget(CS_URL_2 + "|" + "Version 2"); - - element = group.addElement(); - element.setCode("78901"); - element.setDisplay("Source Code 78901"); - - target = element.addTarget(); - target.setCode("34567"); - target.setDisplay("Target Code 34567"); - target.setRelationship(Enumerations.ConceptMapRelationship.SOURCEISNARROWERTHANTARGET); - - return conceptMap; - } - - public static String toSearchUuidFromLinkNext(Bundle theBundle) { - String linkNext = theBundle.getLink("next").getUrl(); - linkNext = linkNext.substring(linkNext.indexOf('?')); - Map params = UrlUtil.parseQueryString(linkNext); - String[] uuidParams = params.get(Constants.PARAM_PAGINGACTION); - return uuidParams[0]; - } - } diff --git a/hapi-fhir-jpaserver-test-utilities/pom.xml b/hapi-fhir-jpaserver-test-utilities/pom.xml index 24fe721cc6b..2fb8f88c176 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaDstu3Test.java index ca3b5303074..4ba98a20fea 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaDstu3Test.java @@ -118,6 +118,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationContext; @@ -348,6 +349,9 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Autowired private IBulkDataExportJobSchedulingHelper myBulkDataScheduleHelper; + @RegisterExtension + private final PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry); + @AfterEach() public void afterCleanupDao() { myDaoConfig.setExpireSearchResults(new DaoConfig().isExpireSearchResults()); @@ -360,7 +364,6 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @AfterEach public void afterResetInterceptors() { super.afterResetInterceptors(); - myInterceptorRegistry.unregisterAllInterceptors(); } @AfterEach diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java index 00d54d7925a..95aa4261ed8 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -517,7 +517,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil private IBulkDataExportJobSchedulingHelper myBulkDataScheduleHelper; @RegisterExtension - private PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry); + private final PreventDanglingInterceptorsExtension myPreventDanglingInterceptorsExtension = new PreventDanglingInterceptorsExtension(()-> myInterceptorRegistry); @AfterEach() public void afterCleanupDao() { @@ -538,8 +538,7 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil @Order(Integer.MIN_VALUE) @BeforeEach public void beforeResetInterceptors() { -// FIXME: restore? -// myInterceptorRegistry.unregisterAllInterceptors(); + // nothing } @Override @@ -548,9 +547,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil public void afterResetInterceptors() { super.afterResetInterceptors(); myInterceptorRegistry.unregisterInterceptor(myPerformanceTracingLoggingInterceptor); - - // FIXME: restore? -// myInterceptorRegistry.unregisterAllInterceptors(); } @AfterEach diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/testutil/SpringFileTest.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/testutil/SpringFileTest.java deleted file mode 100644 index 1f16e0fbffb..00000000000 --- a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/testutil/SpringFileTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package ca.uhn.fhir.jpa.testutil; - -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.Test; - -public class SpringFileTest { - - - @Test - public void testNoBadResources() throws Exception { - String text = IOUtils.toString(SpringFileTest.class.getResourceAsStream("/hapi-fhir-server-resourceproviders-dstu2.xml")); -// assertThat(text, not(containsString("OperationDefinition"))); - } - -} diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 983d182a888..e0a9b637dfc 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 74406036383..74f23de9987 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index d14c31a24fc..6a3f7b9d826 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index a05ff319863..4b6c5c3cbe0 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.3.0-PRE5-SNAPSHOT + 6.3.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 c59d014ba2b..a65f17bb172 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.3.0-PRE5-SNAPSHOT + 6.3.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 7be51f6d371..636eb0b1395 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml @@ -20,7 +20,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT com.github.ben-manes.caffeine diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index ea8eda5e404..d3c3b57b4c9 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.3.0-PRE5-SNAPSHOT + 6.3.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 910d572cc66..0b72941b471 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index 47996d5d96b..c8b7cfdd29d 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../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 9c3e9aef6cd..b1b956e3e58 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.3.0-PRE5-SNAPSHOT + 6.3.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 e5b689b27db..dbf20aa5813 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.3.0-PRE5-SNAPSHOT + 6.3.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 34c3d8a4547..e977907e8bb 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT hapi-fhir-spring-boot-sample-client-okhttp 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 9def3808e97..8cb30c1b1c7 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT hapi-fhir-spring-boot-sample-server-jersey 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 1ac037d7169..108d95fd6db 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT hapi-fhir-spring-boot-samples 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 e693fd2ebb4..0b9ace3ed81 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index de87a1bebc2..29650eb9a7b 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 8e6d8683d1c..680c21c7726 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.3.0-PRE5-SNAPSHOT + 6.3.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 1cb4a3c4b81..fe46883cf68 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml index 7368d5d7fee..a04e5c8760d 100644 --- a/hapi-fhir-storage-batch2/pom.xml +++ b/hapi-fhir-storage-batch2/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index 07ed13bd630..6dfab8ef21b 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index d7fb772809d..d1aef29fb8d 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index 10f4772125a..ebc8f3f25eb 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java index 4c8f92d4e6b..008c459aad8 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/MetadataKeyCurrentlyReindexing.java @@ -21,40 +21,23 @@ package ca.uhn.fhir.jpa.api.dao; */ import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum.ResourceMetadataKeySupportingAnyResource; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseResource; -public final class MetadataKeyCurrentlyReindexing extends ResourceMetadataKeySupportingAnyResource { +public final class MetadataKeyCurrentlyReindexing extends ResourceMetadataKeyEnum { private static final long serialVersionUID = 1L; MetadataKeyCurrentlyReindexing(String theValue) { super(theValue); } - @Override - public Boolean get(IAnyResource theResource) { - return (Boolean) theResource.getUserData(IDao.CURRENTLY_REINDEXING.name()); - } - @Override public Boolean get(IResource theResource) { return (Boolean) theResource.getResourceMetadata().get(IDao.CURRENTLY_REINDEXING); } - @Override - public void put(IAnyResource theResource, Boolean theObject) { - theResource.setUserData(IDao.CURRENTLY_REINDEXING.name(), theObject); - } - - public void put(IBaseResource theResource, Boolean theValue) { - if (theResource instanceof IAnyResource) { - put((IAnyResource) theResource, theValue); - } else { - put((IResource) theResource, theValue); - } - } - @Override public void put(IResource theResource, Boolean theObject) { theResource.getResourceMetadata().put(IDao.CURRENTLY_REINDEXING, theObject); 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 7922dbdb497..620b6236204 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 @@ -1473,12 +1473,7 @@ public abstract class BaseTransactionProcessor { } } - IPrimitiveType deletedInstantOrNull; - if (nextResource instanceof IAnyResource) { - deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) nextResource); - } else { - deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IResource) nextResource); - } + IPrimitiveType deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; IFhirResourceDao dao = myDaoRegistry.getResourceDao(nextResource.getClass()); diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index c8d3e773720..1c1e6b58f52 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index ead0e7a428e..8f3cfeecd42 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index 7c58d7360c3..a8aaf72b5fe 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -44,33 +44,6 @@ - - org.jacoco - jacoco-maven-plugin - - - ${basedir}/target/classes - ${basedir}/../hapi-fhir-base/target/classes - ${basedir}/../hapi-fhir-client/target/classes - ${basedir}/../hapi-fhir-server/target/classes - - - ${basedir}/src/main/java - ${basedir}/../hapi-fhir-base/src/main/java - ${basedir}/../hapi-fhir-client/src/main/java - ${basedir}/../hapi-fhir-server/src/main/java - - true - - - - default-prepare-agent - - prepare-agent - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml index a741082fe19..76b09eb66f5 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index cdc42d9eb54..90ca6bc4664 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 3791b658748..225f4048856 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -15,11 +15,6 @@ HAPI FHIR Structures - FHIR r4b - - com.squareup.okhttp3 - okhttp - true - ca.uhn.hapi.fhir hapi-fhir-base @@ -113,7 +108,11 @@ Optional dependencies used by org.hl7.fhir.r4b We include these here to get the aggregate JavaDoc to work --> - + + com.squareup.okhttp3 + okhttp + true + org.projectlombok lombok diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index edcf552eef2..a79e1a0bbb7 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 5c8559ec5db..4cd8cce5e18 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index e35f60e3bfb..4e7aee71759 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.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 3165748424a..57489171880 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.3.0-PRE5-SNAPSHOT + 6.3.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 f942c0487f8..55941190fad 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.3.0-PRE5-SNAPSHOT + 6.3.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 12a2a9d62d8..9750f4ce0dc 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.3.0-PRE5-SNAPSHOT + 6.3.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 b6ca945f0f4..845ccd9b8c4 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.3.0-PRE5-SNAPSHOT + 6.3.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 ae52667cf94..31105bf2458 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index cbe0e7f6cac..483d85ebf98 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 13c0527c46e..e331aa2d3fe 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java index 2615f49e5dc..563756f199d 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java @@ -5,9 +5,8 @@ import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.tinder.parser.BaseStructureSpreadsheetParser; import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingModel; -import ca.uhn.fhir.tinder.parser.ResourceGeneratorUsingSpreadsheet; +import ca.uhn.fhir.util.ClasspathUtil; import org.apache.commons.lang.WordUtils; -import org.apache.maven.model.Resource; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; @@ -23,9 +22,8 @@ import org.apache.velocity.runtime.RuntimeConstants; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStreamWriter; +import java.io.Reader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -147,10 +145,6 @@ public class TinderJpaRestServerMojo extends AbstractMojo { gen.setTemplate("/vm/jpa_resource_provider.vm"); gen.writeAll(packageDirectoryBase, null, packageBase); - // gen.setFilenameSuffix("ResourceTable"); - // gen.setTemplate("/vm/jpa_resource_table.vm"); - // gen.writeAll(directoryBase, packageBase); - } catch (Exception e) { throw new MojoFailureException(Msg.code(115) + "Failed to generate server", e); } @@ -182,30 +176,12 @@ public class TinderJpaRestServerMojo extends AbstractMojo { v.setProperty("resource.loader.cp.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); v.setProperty("runtime.strict_mode.enable", Boolean.TRUE); - - /* - * Spring XML - */ - InputStream templateIs = ResourceGeneratorUsingSpreadsheet.class.getResourceAsStream("/vm/jpa_spring_beans.vm"); - InputStreamReader templateReader = new InputStreamReader(templateIs); - targetResourceDirectory.mkdirs(); - File f = new File(targetResourceDirectory, targetResourceSpringBeansFile); - OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(f, false), "UTF-8"); - v.evaluate(ctx, w, "", templateReader); - w.close(); - - Resource resource = new Resource(); - resource.setDirectory(targetResourceDirectory.getAbsolutePath()); - resource.addInclude(targetResourceSpringBeansFile); - myProject.addResource(resource); - /* * Spring Java */ - templateIs = ResourceGeneratorUsingSpreadsheet.class.getResourceAsStream("/vm/jpa_spring_beans_java.vm"); - templateReader = new InputStreamReader(templateIs); - f = new File(configPackageDirectoryBase, "GeneratedDaoAndResourceProviderConfig" + capitalize + ".java"); - w = new OutputStreamWriter(new FileOutputStream(f, false), "UTF-8"); + Reader templateReader = ClasspathUtil.loadResourceAsReader("/vm/jpa_spring_beans_java.vm"); + File f = new File(configPackageDirectoryBase, "GeneratedDaoAndResourceProviderConfig" + capitalize + ".java"); + OutputStreamWriter w = new OutputStreamWriter(new FileOutputStream(f, false), "UTF-8"); v.evaluate(ctx, w, "", templateReader); w.close(); diff --git a/hapi-tinder-plugin/src/main/resources/vm/client.vm b/hapi-tinder-plugin/src/main/resources/vm/client.vm deleted file mode 100644 index fb6d5e84dc9..00000000000 --- a/hapi-tinder-plugin/src/main/resources/vm/client.vm +++ /dev/null @@ -1,70 +0,0 @@ - -package ${packageBase}; - -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.dstu.composite.*; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.rest.annotation.*; -import ca.uhn.fhir.rest.client.api.*; -import ${packageBase}.resource.*; - -/** - * Restful Client Definition Class - */ -public interface ${className} extends IBasicClient { - -#foreach ( $resource in ${resources} ) -#if ( ${resource.hasReadOperation} ) - /** - * Retrieves a single ${resource.resourceType} resource by - * its ID. -#if ( ${resource.readOperation.hasDocumentation} ) - *

    - * Documentation: ${resource.readOperation.documentation} - *

    -#end - * @param theId The ID to use. - * @see See {@link IdDt} for examples of how to use the ID datatype - * @see See the FHIR read operation definition. - */ - @Read - ${resource.resourceType} get${resource.resourceType}ById(@Read.IdParam IdDt theId); - -#end -#if ( ${resource.hasVReadOperation} ) - /** - * Retrieves a single ${resource.resourceType} resource by - * its ID and version ID. -#if ( ${resource.readOperation.hasDocumentation} ) - *

    - * Documentation: ${resource.readOperation.documentation} - *

    -#end - * @param theId The ID to use. - * @param theVersionId The ID to use. - * @see See {@link IdDt} for examples of how to use the ID datatype - * @see See the FHIR read operation definition. - */ - @Read - ${resource.resourceType} get${resource.resourceType}ById(@Read.IdParam IdDt theId, @Read.VersionIdParam IdDt theVersionId); - -#end -#if ( ${resource.hasSearchOperation} ) -#foreach ( $param in $resource.searchParams ) - /** - * Searches for ${resource.resourceType} resource(s) -#if ( ${resource.readOperation.hasDocumentation} ) - *

    - * Documentation: ${resource.readOperation.documentation} - *

    -#end - */ - @Search(type=${resource.resourceType}.class) - Bundle search${resource.resourceType}By${param.nameCapitalized}(@Required(name=${resource.resourceType}.${param.constantName}) StringDt the${param.nameCapitalized}); - -#end -#end -#end - -} - diff --git a/hapi-tinder-plugin/src/main/resources/vm/dt_composite_dstu.vm b/hapi-tinder-plugin/src/main/resources/vm/dt_composite_dstu.vm deleted file mode 100644 index 3904f684e7f..00000000000 --- a/hapi-tinder-plugin/src/main/resources/vm/dt_composite_dstu.vm +++ /dev/null @@ -1,233 +0,0 @@ -#parse ( "/vm/templates_dstu.vm" ) - -package ${packageBase}.composite; - -import java.math.BigDecimal; -import org.apache.commons.lang3.StringUtils; -import java.util.*; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.primitive.*; -import ca.uhn.fhir.model.api.annotation.*; -import ca.uhn.fhir.model.base.composite.*; - -#foreach ( $import in $imports ) -import ${import}; -#end - -/** - * HAPI/FHIR ${className} Datatype - * (${shortName}) - * - *

    - * Definition: - * ${definition} - *

    - * - *

    - * Requirements: - * ${requirements} - *

    - */ -@DatatypeDef(name="${className}") -public class ${className} - extends #{if}( ${className}=="ResourceReferenceDt" || ${className}=="IdentifierDt" || ${className}=="CodingDt" || ${className}=='QuantityDt' || ${className}=='HumanNameDt') Base${className} #{else} BaseIdentifiableElement #{end} - implements ICompositeDatatype -{ - - /** - * Constructor - */ - public ${className}() { - // nothing - } - -######################### -### Type-specific constructors -######################### -#if ( ${className} == "CodeableConceptDt" ) - /** - * Constructor which creates a CodeableConceptDt with one coding repetition, containing - * the given system and code - */ - public CodeableConceptDt(String theSystem, String theCode) { - addCoding().setSystem(theSystem).setCode(theCode); - } -#end -#if ( ${className} == "CodingDt" ) - /** - * Creates a new Coding with the given system and code - */ - public CodingDt(String theSystem, String theCode) { - setSystem(theSystem); - setCode(theCode); - } - - /** - * Copy constructor: Creates a new Coding with the system and code copied out of the given coding - */ - public CodingDt(BaseCodingDt theCoding) { - this(theCoding.getSystemElement().getValueAsString(), theCoding.getCodeElement().getValue()); - } - -#end -#if ( ${className} == "ContactDt" ) - /** - * Constructor - */ - @SimpleSetter - public ContactDt(@SimpleSetter.Parameter(name="theValue") String theValue) { - setValue(theValue); - } - - /** - * Constructor - */ - @SimpleSetter - public ContactDt(@SimpleSetter.Parameter(name="theContactUse") ContactUseEnum theContactUse, @SimpleSetter.Parameter(name="theValue") String theValue) { - setUse(theContactUse); - setValue(theValue); - } -#end -#if ( ${className} == "IdentifierDt" ) - /** - * Creates a new identifier with the given system and value - */ - @SimpleSetter - public IdentifierDt(@SimpleSetter.Parameter(name="theSystem") String theSystem, @SimpleSetter.Parameter(name="theValue") String theValue) { - setSystem(theSystem); - setValue(theValue); - } - - /** - * Creates a new identifier with the given system and value - */ - @SimpleSetter - public IdentifierDt(@SimpleSetter.Parameter(name="theUse") IdentifierUseEnum theUse, @SimpleSetter.Parameter(name="theSystem") String theSystem, @SimpleSetter.Parameter(name="theValue") String theValue, @SimpleSetter.Parameter(name="theLabel") String theLabel) { - setUse(theUse); - setSystem(theSystem); - setValue(theValue); - setLabel(theLabel); - } - - @Override - public String toString() { - return "IdentifierDt[" + getValue() + "]"; - } -#end -#if ( ${className} == "QuantityDt" ) -#{if}(${version}=="dstu") #{set}($ce="QuantityCompararatorEnum") #{else} #{set}($ce="QuantityComparatorEnum") #{end} - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name="theValue") double theValue) { - setValue(theValue); - } - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name="theValue") long theValue) { - setValue(theValue); - } - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name = "theComparator") ${ce} theComparator, @SimpleSetter.Parameter(name = "theValue") double theValue, - @SimpleSetter.Parameter(name = "theUnits") String theUnits) { - setValue(theValue); - setComparator(theComparator); - setUnits(theUnits); - } - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name = "theComparator") ${ce} theComparator, @SimpleSetter.Parameter(name = "theValue") long theValue, - @SimpleSetter.Parameter(name = "theUnits") String theUnits) { - setValue(theValue); - setComparator(theComparator); - setUnits(theUnits); - } - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name="theComparator") ${ce} theComparator, @SimpleSetter.Parameter(name="theValue") double theValue, @SimpleSetter.Parameter(name="theSystem") String theSystem, @SimpleSetter.Parameter(name="theUnits") String theUnits) { - setValue(theValue); - setComparator(theComparator); - setSystem(theSystem); - setUnits(theUnits); - } - - /** - * Constructor - */ - @SimpleSetter - public QuantityDt(@SimpleSetter.Parameter(name="theComparator") ${ce} theComparator, @SimpleSetter.Parameter(name="theValue") long theValue, @SimpleSetter.Parameter(name="theSystem") String theSystem, @SimpleSetter.Parameter(name="theUnits") String theUnits) { - setValue(theValue); - setComparator(theComparator); - setSystem(theSystem); - setUnits(theUnits); - } - -#end -#if ( ${className} == "ResourceReferenceDt" ) - /** - * Constructor which creates a resource reference containing the actual resource in question. - *

    - * When using this in a server: Generally if this is serialized, it will be serialized as a contained - * resource, so this should not be used if the intent is not to actually supply the referenced resource. This is not - * a hard-and-fast rule however, as the server can be configured to not serialized this resource, or to load an ID - * and contain even if this constructor is not used. - *

    - * - * @param theResource - * The resource instance - */ - public ResourceReferenceDt(IResource theResource) { - super(theResource); - } - - /** - * Constructor which accepts a reference directly (this can be an ID, a partial/relative URL or a complete/absolute - * URL) - * - * @param theId - * The reference itself - */ - public ResourceReferenceDt(String theId) { - setReference(new IdDt(theId)); - } - - /** - * Constructor which accepts a reference directly (this can be an ID, a partial/relative URL or a complete/absolute - * URL) - * - * @param theId - * The reference itself - */ - public ResourceReferenceDt(org.hl7.fhir.instance.model.api.IIdType theResourceId) { - setReference(theResourceId); - } -#end - -#childExtensionFields( $childExtensionTypes ) -#childVars( $children ) -#childAccessors( $children ) -#childResourceBlocks($resourceBlockChildren) - -######################### -### Type-specific methods -######################### -#if ( ${className} == "HumanNameDt" ) -#end -#childExtensionTypes( $childExtensionTypes ) - -} \ No newline at end of file diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm deleted file mode 100644 index 1390f1dc1eb..00000000000 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans.vm +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - -#if ( ${versionCapitalized} != 'Dstu1' ) - - - - - - - -#end - - -#foreach ( $res in $resources ) - -#end - - -#foreach ( $res in $resources ) - -#elseif ( ${versionCapitalized} == 'R4' && ${res.name} == 'ConceptMap') - class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}"> -#elseif ( ${versionCapitalized} != 'Dstu1' && ( ${res.name} == 'Bundle' || ${res.name} == 'Encounter' || ${res.name} == 'Composition' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'QuestionnaireResponse' || ${res.name} == 'SearchParameter')) - class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}"> -#elseif ( ${versionCapitalized} != 'Dstu1' && ${versionCapitalized} != 'Dstu2' && ${res.name} == 'Observation') - class="ca.uhn.fhir.jpa.dao.FhirResourceDao${res.name}${versionCapitalized}"> -#else - class="ca.uhn.fhir.jpa.dao.FhirResourceDao${versionCapitalized}"> -#end - - - - - - - - -#end - - diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm index 312d52886d4..806f9f563b2 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_spring_beans_java.vm @@ -57,25 +57,20 @@ FhirContext myFhirContext; public #if ( (${versionCapitalized.startsWith('R')} || ${versionCapitalized} == 'Dstu3') && (${res.name} == 'ConceptMap') ) IFhirResourceDao${res.name} -#elseif ( ${res.name} == 'CodeSystem' || ${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'StructureDefinition' || ${res.name} == 'ValueSet' ) - IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}> -#elseif ( ${versionCapitalized} != 'Dstu2' && (${res.name} == 'Observation')) +#elseif ( ${res.name} == 'CodeSystem' || ${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Everything' || ${res.name} == 'Observation' || ${res.name} == 'Patient' || ${res.name} == 'Subscription' || ${res.name} == 'SearchParameter' || ${res.name} == 'StructureDefinition' || ${res.name} == 'ValueSet' ) IFhirResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}> #else IFhirResourceDao<${resourcePackage}.${res.declaringClassNameComplete}> #end dao${res.declaringClassNameComplete}${versionCapitalized}() { -#if ( ${res.name} == 'Bundle' || ${res.name} == 'CodeSystem' || ${res.name} == 'Encounter' || ${res.name} == 'Patient' || ${res.name} == 'ValueSet' ) +#if ( ${res.name} == 'Bundle' || ${res.name} == 'CodeSystem' || ${res.name} == 'Composition' || ${res.name} == 'Encounter' || ${res.name} == 'Observation' || ${res.name} == 'Patient' || ${res.name} == 'SearchParameter' || ${res.name} == 'ValueSet' ) ca.uhn.fhir.jpa.dao.JpaResourceDao${res.name}<${resourcePackage}.${res.declaringClassNameComplete}> retVal; retVal = new ca.uhn.fhir.jpa.dao.JpaResourceDao${res.name}<>(); #elseif ( (${versionCapitalized.startsWith('R')} || ${versionCapitalized} == 'Dstu3') && (${res.name} == 'ConceptMap') ) ca.uhn.fhir.jpa.dao.JpaResourceDao${res.name} retVal; retVal = new ca.uhn.fhir.jpa.dao.JpaResourceDao${res.name}<>(); -#elseif ( ${res.name} == 'Everything' || ${res.name} == 'Subscription' || ${res.name} == 'ValueSet' || ${res.name} == 'SearchParameter' || ${res.name} == 'CodeSystem' || ${res.name} == 'Composition' || ${res.name} == 'StructureDefinition') - ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal; - retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}(); -#elseif ( ${versionCapitalized} != 'Dstu1' && ${versionCapitalized} != 'Dstu2' && ${res.name} == 'Observation') +#elseif ( ${res.name} == 'Everything' || ${res.name} == 'Subscription' || ${res.name} == 'StructureDefinition') ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized} retVal; retVal = new ca.uhn.fhir.jpa.dao${package_suffix}.FhirResourceDao${res.name}${versionCapitalized}(); #else diff --git a/hapi-tinder-plugin/src/main/resources/vm/resource_dstu.vm b/hapi-tinder-plugin/src/main/resources/vm/resource_dstu.vm deleted file mode 100644 index 498163abcf1..00000000000 --- a/hapi-tinder-plugin/src/main/resources/vm/resource_dstu.vm +++ /dev/null @@ -1,99 +0,0 @@ -#parse ( "/vm/templates_dstu.vm" ) - -package ${packageBase}.resource; - - -import java.util.*; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.api.annotation.*; -import ca.uhn.fhir.rest.gclient.*; - -#foreach ( $import in $imports ) -import ${import}; -#end - -##import ${packageBase}.composite.*; -##import ${packageBase}.valueset.*; - -/** - * HAPI/FHIR ${elementName} Resource - * (${shortName}) - * - *

    - * Definition: - * ${definition} - *

    - * - *

    - * Requirements: - * ${requirements} - *

    - * -#if (${profile} != "") - *

    - * Profile Definition: - * ${profile} - *

    - * -#end - */ -@ResourceDef(name="${elementName}", profile="${profile}", id="${id}") -public class ${className} extends ca.uhn.fhir.model.${version}.resource.BaseResource - implements #{if}( ${className}=="OperationOutcome" || ${className}=="Conformance" || ${className}=="SecurityEvent" ) ca.uhn.fhir.model.base.resource.Base${className} #{else} IResource #{end} - { - -#foreach ( $param in $searchParams ) - /** - * Search parameter constant for $esc.html(${param.name}) - *

    - * Description: $esc.html(${param.description})
    - * Type: $esc.html(${param.type})
    - * Path: $esc.html(${param.path})
    - *

    - */ - @SearchParamDefinition(name="${param.name}", path="${param.path}", description="${param.description}", type="${param.type}" #{if}($param.compositeOf.empty == false) , compositeOf={ #{foreach}($compositeOf in $param.compositeOf) "${compositeOf}"#{if}($foreach.hasNext), #{end}#{end} } #{end} ) - public static final String $param.constantName = "${param.name}"; - - /** - * Fluent Client search parameter constant for $esc.html(${param.name}) - *

    - * Description: $esc.html(${param.description})
    - * Type: $esc.html(${param.type})
    - * Path: $esc.html(${param.path})
    - *

    - */ -#if( ${param.typeCapitalized} == 'Composite' ) - public static final CompositeClientParam<${param.compositeTypes[0]}ClientParam, ${param.compositeTypes[1]}ClientParam> ${param.fluentConstantName} = new CompositeClientParam<${param.compositeTypes[0]}ClientParam, ${param.compositeTypes[1]}ClientParam>(${param.constantName}); -#else - public static final ${param.typeCapitalized}ClientParam ${param.fluentConstantName} = new ${param.typeCapitalized}ClientParam(${param.constantName}); -#end - -#if( ${param.typeCapitalized} == 'Reference' ) -#foreach ( $include in $param.paths ) - /** - * Constant for fluent queries to be used to add include statements. Specifies - * the path value of "${include.path}". - */ - public static final Include INCLUDE_${include.includeName} = new Include("${include.path}"); - -#end -#end -#end - -#childExtensionFields( $childExtensionTypes ) -#childVars( $children ) -#childAccessors( $children ) -#childResourceBlocks($resourceBlockChildren) - -#childExtensionTypes( $childExtensionTypes ) - - @Override - public String getResourceName() { - return "${className.replaceAll("Resource", "")}"; - } - - public ca.uhn.fhir.context.FhirVersionEnum getStructureFhirVersionEnum() { - return ca.uhn.fhir.context.FhirVersionEnum.${versionEnumName}; - } - -} diff --git a/hapi-tinder-plugin/src/main/resources/vm/templates_dstu.vm b/hapi-tinder-plugin/src/main/resources/vm/templates_dstu.vm deleted file mode 100644 index 07aabdd053c..00000000000 --- a/hapi-tinder-plugin/src/main/resources/vm/templates_dstu.vm +++ /dev/null @@ -1,361 +0,0 @@ -################################################################## -## childVars -################################################################## - -#macro ( childVars $childElements ) -#foreach ( $child in $childElements ) -#if (${child.resourceRef} || ${child.hasMultipleTypes}) - @Child(name="${child.elementNameSimplified}", order=${foreach.index}, min=${child.cardMin}, max=${child.cardMaxForChildAnnotation}, summary=${child.summary}, modifier=${child.modifier}, type={ -#foreach ($nextType in ${child.referenceTypesForMultiple}) -#if ( ${child.resourceRef} && ${nextType} != "IResource" ) - ${nextType}.class#{if}($foreach.hasNext), #{end} -#else - ${nextType}.class#{if}($foreach.hasNext), #{end} -#end -#end - }) -#elseif ($!child.block) - @Child(name="${child.elementNameSimplified}", order=${foreach.index}, min=${child.cardMin}, max=${child.cardMaxForChildAnnotation}, summary=${child.summary}, modifier=${child.modifier}) -#else - @Child(name="${child.elementNameSimplified}", type=${child.annotationType}.class, order=${foreach.index}, min=${child.cardMin}, max=${child.cardMaxForChildAnnotation}, summary=${child.summary}, modifier=${child.modifier}) -#end -#if ( $child.hasExtensionUrl ) - @Extension(url = "${child.extensionUrl}", isModifier=${child.extensionModifier}, definedLocally=${child.extensionLocal}) -#end -#if (${includeDescriptionAnnotations}) - @Description( - shortDefinition="${child.shortName}", - formalDefinition="${child.definition}" - ) -#end - private ${child.referenceType} ${child.variableName}; - -#end -#end - - -################################################################## -## childAccessors -################################################################## - -#macro ( childAccessors $childElements ) - - @Override - public boolean isEmpty() { - return super.isBaseEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty( #{foreach}($child in $childElements) ${child.variableName}#{if}($foreach.hasNext), #{end}#{end}); - } - - @Override - public List getAllPopulatedChildElementsOfType(Class theType) { - return ca.uhn.fhir.util.ElementUtil.allPopulatedChildElements(theType, #{foreach}($child in $childElements)${child.variableName}#{if}($foreach.hasNext), #{end}#{end}); - } - -#foreach ( $child in $childElements ) -#if (${child.methodName} != 'Meta') - /** - * Gets the value(s) for ${child.elementName} ($esc.html(${child.shortName})). - * creating it if it does - * not exist. Will not return null. - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.referenceType} get${child.methodName}() { -#if ( (${child.hasMultipleTypes} == false && ${child.singleChildInstantiable} == true) || (${child.resourceRef}) ) - if (${child.variableName} == null) { -#if ( ${child.boundCode} && ${child.repeatable} == false ) - ${child.variableName} = new ${child.referenceTypeForConstructor}(${child.bindingClass}.VALUESET_BINDER); -#else - ${child.variableName} = new ${child.referenceTypeForConstructor}(); -#end - } -#end - return ${child.variableName}; - } -#end - - /** - * Gets the value(s) for ${child.elementName} ($esc.html(${child.shortName})). - * creating it if it does - * not exist. Will not return null. - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.referenceType} get${child.methodName}Element() { -#if ( (${child.hasMultipleTypes} == false && ${child.singleChildInstantiable} == true) || (${child.resourceRef}) ) - if (${child.variableName} == null) { -#if ( ${child.boundCode} && ${child.repeatable} == false ) - ${child.variableName} = new ${child.referenceTypeForConstructor}(${child.bindingClass}.VALUESET_BINDER); -#else - ${child.variableName} = new ${child.referenceTypeForConstructor}(); -#end - } -#end - return ${child.variableName}; - } - - - /** - * Sets the value(s) for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.declaringClassNameComplete} set${child.methodName}(${child.referenceType} theValue) { - ${child.variableName} = theValue; - return this; - } - -#if( ${child.repeatable} && ${child.singleChildInstantiable} && ${child.resourceRef} == false && ${child.boundCode} == false ) - /** - * Adds and returns a new value for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.singleType} add${child.methodName}() { - ${child.singleType} newType = new ${child.singleType}(); - get${child.methodName}().add(newType); - return newType; - } - - /** - * Gets the first repetition for ${child.elementName} ($esc.html(${child.shortName})), - * creating it if it does not already exist. - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.singleType} get${child.methodName}FirstRep() { - if (get${child.methodName}().isEmpty()) { - return add${child.methodName}(); - } - return get${child.methodName}().get(0); - } -#end -#if( ${child.repeatable} && ${child.singleChildInstantiable} && ${child.resourceRef} ) - /** - * Adds and returns a new value for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ResourceReferenceDt add${child.methodName}() { - ResourceReferenceDt newType = new ResourceReferenceDt(); - get${child.methodName}().add(newType); - return newType; - } -#end -#if ( ${child.boundCode} && ${child.repeatable} ) - /** - * Add a value for ${child.elementName} ($esc.html(${child.shortName})) using an enumerated type. This - * is intended as a convenience method for situations where the FHIR defined ValueSets are mandatory - * or contain the desirable codes. If you wish to use codes other than those which are built-in, - * you may also use the {@link ${hash}add${child.methodName}()} method. - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.boundDatatype}<${child.bindingClass}> add${child.methodName}(${child.bindingClass} theValue) { - ${child.boundDatatype}<${child.bindingClass}> retVal = new ${child.boundDatatype}<${child.bindingClass}>(${child.bindingClass}.VALUESET_BINDER, theValue); - get${child.methodName}().add(retVal); - return retVal; - } - - /** - * Gets the first repetition for ${child.elementName} ($esc.html(${child.shortName})), - * creating it if it does not already exist. - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.boundDatatype}<${child.bindingClass}> get${child.methodName}FirstRep() { - if (get${child.methodName}().size() == 0) { - add${child.methodName}(); - } - return get${child.methodName}().get(0); - } - - /** - * Add a value for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.boundDatatype}<${child.bindingClass}> add${child.methodName}() { - ${child.boundDatatype}<${child.bindingClass}> retVal = new ${child.boundDatatype}<${child.bindingClass}>(${child.bindingClass}.VALUESET_BINDER); - get${child.methodName}().add(retVal); - return retVal; - } - - /** - * Sets the value(s), and clears any existing value(s) for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.declaringClassNameComplete} set${child.methodName}(${child.bindingClass} theValue) { - get${child.methodName}().clear(); - add${child.methodName}(theValue); - return this; - } - -#elseif ( ${child.boundCode} ) - /** - * Sets the value(s) for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.declaringClassNameComplete} set${child.methodName}(${child.bindingClass} theValue) { - get${child.methodName}().setValueAsEnum(theValue); - return this; - } - -#end ##if (child.boundCode) -#foreach ( $ss in $child.simpleSetters ) -#if(${child.repeatable}) - /** - * Adds a new value for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - * - * @return Returns a reference to this object, to allow for simple chaining. - */ - public ${child.declaringClassNameComplete} add${child.methodName}(#{foreach}($param in $ss.parameters) ${param.datatype} ${param.parameter}#{if}( $foreach.hasNext ), #{end}#{end}) { - if (${child.variableName} == null) { - ${child.variableName} = new ${child.referenceTypeForConstructor}(); - } - ${child.variableName}.add(new ${ss.datatype}(#{foreach}($param in $ss.parameters)${param.parameter}#{if}( $foreach.hasNext ), #{end}#{end})); - return this; - } -#else - /** - * Sets the value for ${child.elementName} ($esc.html(${child.shortName})) - * - *

    - * Definition: - * $esc.html(${child.definition}) - *

    - */ - public ${child.declaringClassNameComplete} set${child.methodName}${ss.suffix}(#{foreach}($param in $ss.parameters) ${param.datatype} ${param.parameter}#{if}( $foreach.hasNext ), #{end}#{end}) { - ${child.variableName} = new ${ss.datatype}(#{foreach}($param in $ss.parameters)${param.parameter}#{if}( $foreach.hasNext ), #{end}#{end}); - return this; - } -#end - -#end ##foreach-child-in-simplesetters - -#end -#end - -################################################################## -## childExtensionFields -################################################################## - -#macro ( childExtensionFields $childExtensionTypes ) -#foreach ( $extensionType in $childExtensionTypes ) - @Child(name="$extensionType.name", type=${extensionType.annotationType}.class, order=${foreach.index}, min=${extensionType.cardMin}, max=${extensionType.cardMaxForChildAnnotation}) - @Extension(url="${extensionType.url}") - private ${extensionType.referenceType} ${extensionType.variableName}; - -#end -#end - - -################################################################## -## childExtensionTypes -################################################################## - -#macro ( childExtensionTypes $childExtensionTypes ) -#foreach ( $extensionType in $childExtensionTypes ) -#if ( $extensionType.hasChildExtensions ) - @Block() - public static class ${extensionType.nameType} implements IExtension { - -#foreach ( $childExtensionSubtype in $extensionType.childExtensions ) - @Child(name="$childExtensionSubtype.name", type=${childExtensionSubtype.annotationType}.class, order=${foreach.index}, min=${childExtensionSubtype.cardMin}, max=${childExtensionSubtype.cardMaxForChildAnnotation}) - @Extension(url="${childExtensionSubtype.url}") - private ${childExtensionSubtype.referenceType} ${childExtensionSubtype.variableName}; - -#end - - - } - -#foreach ( $extensionSubType in $extensionTypes.childExtensionsWithChildren ) -#childExtensionTypes( $extensionSubType ) -#end -#end -#end -#end - - - -################################################################## -## childResourceBlocks -################################################################## - -#macro ( childResourceBlocks $resourceBlockChildren ) -#foreach ( $blockChild in $resourceBlockChildren ) - /** - * Block class for child element: ${blockChild.name} (${blockChild.shortName}) - * - *

    - * Definition: - * ${blockChild.definition} - *

    - */ - @Block() - public static class ${blockChild.className} - extends #{if}( ${className}=="OperationOutcome" && ${blockChild.className}=="Issue" ) Base${blockChild.className} #{else} BaseIdentifiableElement #{end} - implements IResourceBlock { - -#childVars( $blockChild.children ) -#childAccessors( $blockChild.children ) - -#if ( ${className} == "OperationOutcome" && ${blockChild.className} == "Issue" ) - /** - * This method actually populated {@link ${hash}setDetails(String) OperationOutcome.details} but - * it is provided for consistency with DSTU2+ - */ - @Override - public BaseIssue setDiagnostics(String theString) { - return setDetails(theString); - } -#end - - } - -#childResourceBlocks( $blockChild.resourceBlockChildren ) - -#end -#end - - diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 11c48358944..73ecffb1586 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 737d0de5cbe..a12a0aca3b7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. https://hapifhir.io @@ -1320,7 +1320,7 @@ net.sourceforge.htmlunit htmlunit - 2.65.1 + 2.67.0 @@ -1541,7 +1541,7 @@ com.fasterxml.woodstox woodstox-core - 6.3.1 + 6.4.0 org.ebaysf.web @@ -2102,7 +2102,7 @@ ca.uhn.hapi.fhir hapi-fhir-checkstyle - 6.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT
    @@ -2700,24 +2700,6 @@ - - diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index afa2b2bbf9e..94e569068be 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.0-PRE5-SNAPSHOT + 6.3.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 afa380d3ce3..aa75fd9d8a8 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.3.0-PRE5-SNAPSHOT + 6.3.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 45b78c07508..d6cfcd2b940 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.3.0-PRE5-SNAPSHOT + 6.3.1-SNAPSHOT ../../pom.xml From 50ca94ededecb90e29de6393e617d72253cc880e Mon Sep 17 00:00:00 2001 From: James Agnew Date: Mon, 28 Nov 2022 07:52:12 -0500 Subject: [PATCH 2/2] Improve JPA response messages (#4293) * Improve JPA response messages * FIxes * Test fixes * Test fixes * Ongoing testing * Work on messages * Work on messages * Add valueset * Add response code enum * Version bump * Undo bump * Improve changelog * Test fixes * Add javadocs * Version bump HAPI * Test fixes * Test fix * Test fixes * Test fixes * Account for review changes * Test fix * Docs fix * Work on API * Improve SqlQuery api --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../DefaultProfileValidationSupport.java | 16 + .../ca/uhn/fhir/model/api/ICodingEnum.java | 29 + .../model/api/StorageResponseCodeEnum.java | 72 ++ .../java/ca/uhn/fhir/util/BundleBuilder.java | 277 +++++-- .../uhn/fhir/util/OperationOutcomeUtil.java | 36 +- .../ca/uhn/fhir/i18n/hapi-messages.properties | 20 +- 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/hapi-fhir-cli-jpaserver/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/docs/BundleBuilderExamples.java | 52 ++ ...d-conditional-delete-to-bundlebuilder.yaml | 6 + ...improved-operationoutcome-for-jpa-cud.yaml | 11 + ...-carried-tags-in-transaction-response.yaml | 7 + .../hapi/fhir/docs/model/bundle_builder.md | 18 +- hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jpa/pom.xml | 2 +- .../java/ca/uhn/fhir/jpa/util/SqlQuery.java | 19 +- .../java/ca/uhn/fhir/jpa/util/TestUtil.java | 2 +- hapi-fhir-jpaserver-base/pom.xml | 2 +- .../fhir/jpa/config/Batch2SupportConfig.java | 3 +- .../ca/uhn/fhir/jpa/config/JpaConfig.java | 8 +- .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 529 +++----------- .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 294 +++----- .../fhir/jpa/dao/BaseHapiFhirSystemDao.java | 51 +- .../uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java | 1 + .../jpa/dao/IJpaStorageResourceParser.java | 52 ++ .../jpa/dao/JpaStorageResourceParser.java | 490 +++++++++++++ .../dao/expunge/ResourceExpungeService.java | 23 +- .../delete/batch2/DeleteExpungeSvcImpl.java | 3 +- .../search/PersistedJpaBundleProvider.java | 10 +- .../PersistedJpaBundleProviderFactory.java | 33 + .../jpa/search/builder/SearchBuilder.java | 47 +- .../ca/uhn/fhir/jpa/term/TermReadSvcImpl.java | 9 +- hapi-fhir-jpaserver-cql/pom.xml | 2 +- .../pom.xml | 2 +- hapi-fhir-jpaserver-mdm/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 2 +- .../model/cross/IBasePersistedResource.java | 9 +- .../fhir/jpa/model/entity/ModelConfig.java | 25 + .../model/entity/ResourceHistoryTable.java | 10 + .../fhir/jpa/model/entity/ResourceTable.java | 10 + 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 +- .../dstu3/FhirResourceDaoDstu3UpdateTest.java | 8 +- .../dstu3/ResourceProviderDstu3Test.java | 10 +- ...temProviderTransactionSearchDstu3Test.java | 6 +- hapi-fhir-jpaserver-test-r4/pom.xml | 2 +- .../uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java | 4 +- .../r4/FhirResourceDaoR4InterceptorTest.java | 2 +- .../jpa/dao/r4/FhirResourceDaoR4TagsTest.java | 117 +++ .../dao/r4/FhirResourceDaoR4UpdateTest.java | 17 +- ...TerminologyTranslationInterceptorTest.java | 11 +- ...roviderMeaningfulOutcomeMessageR4Test.java | 686 ++++++++++++++++++ .../provider/r4/ResourceProviderR4Test.java | 237 +++--- .../stresstest/GiantTransactionPerfTest.java | 5 +- .../uhn/fhir/jpa/term/ITermReadSvcTest.java | 9 +- hapi-fhir-jpaserver-test-r4b/pom.xml | 2 +- hapi-fhir-jpaserver-test-r5/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- .../ca/uhn/fhir/jpa/test/BaseJpaR4Test.java | 6 - .../jpa/test/PatientReindexTestHelper.java | 2 +- .../jpa/term/LoincFullLoadR4SandboxIT.java | 6 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 2 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/pom.xml | 2 +- .../server/storage/TransactionDetails.java | 9 + .../auth/AuthorizationInterceptor.java | 4 + .../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/export/ExpandResourcesStep.java | 22 +- .../jobs/export/ExpandResourcesStepTest.java | 4 + hapi-fhir-storage-batch2/pom.xml | 2 +- hapi-fhir-storage-mdm/pom.xml | 2 +- hapi-fhir-storage-test-utilities/pom.xml | 2 +- hapi-fhir-storage/pom.xml | 2 +- .../java/ca/uhn/fhir/jpa/api/dao/IDao.java | 4 +- .../fhir/jpa/api/dao/IFhirResourceDao.java | 9 + .../java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java | 6 +- .../fhir/jpa/api/model/DaoMethodOutcome.java | 19 + .../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 117 ++- .../fhir/jpa/dao/BaseStorageResourceDao.java | 207 ++++++ .../jpa/dao/BaseTransactionProcessor.java | 56 +- .../uhn/fhir/jpa/dao/EntriesToProcessMap.java | 11 +- .../fhir/jpa/dao/IStorageResourceParser.java | 41 ++ 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 +- .../ca/uhn/fhir/util/BundleBuilderTest.java | 50 ++ 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-r5/pom.xml | 2 +- hapi-fhir-validation/pom.xml | 2 +- .../support/HapiFhirStorageResponseCode.json | 60 ++ .../HapiFhirCodeSystemGeneratorTest.java | 81 +++ .../FhirInstanceValidatorR4Test.java | 64 ++ hapi-tinder-plugin/pom.xml | 2 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 6 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 136 files changed, 3134 insertions(+), 1082 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodingEnum.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/StorageResponseCodeEnum.java create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-add-conditional-delete-to-bundlebuilder.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-improved-operationoutcome-for-jpa-cud.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-include-carried-tags-in-transaction-response.yaml create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaStorageResourceParser.java create mode 100644 hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java create mode 100644 hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderMeaningfulOutcomeMessageR4Test.java create mode 100644 hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageResourceDao.java create mode 100644 hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IStorageResourceParser.java create mode 100644 hapi-fhir-validation/src/main/resources/org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json create mode 100644 hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/HapiFhirCodeSystemGeneratorTest.java diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index 17da3b8c614..06a732ff02c 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index cff510d154e..f3fa6ed28ea 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index 03b77f17e1a..005235ccd16 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java index ccafcf2fdfe..257080e74d5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java @@ -22,10 +22,13 @@ package ca.uhn.fhir.context.support; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.util.BundleUtil; +import ca.uhn.fhir.util.ClasspathUtil; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; @@ -321,6 +324,19 @@ public class DefaultProfileValidationSupport implements IValidationSupport { } else { ourLog.warn("Unable to load resource: {}", theClasspath); } + + // Load built-in system + + if (myCtx.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { + String storageCodeEnum = ClasspathUtil.loadResource("org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json"); + IBaseResource storageCodeCodeSystem = myCtx.newJsonParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(storageCodeEnum); + String url = myCtx.newTerser().getSinglePrimitiveValueOrNull(storageCodeCodeSystem, "url"); + theCodeSystems.put(url, storageCodeCodeSystem); + } + + + + } private void loadStructureDefinitions(Map theCodeSystems, String theClasspath) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodingEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodingEnum.java new file mode 100644 index 00000000000..5ee7161d130 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ICodingEnum.java @@ -0,0 +1,29 @@ +package ca.uhn.fhir.model.api; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2022 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% + */ + +public interface ICodingEnum { + + String getCode(); + String getSystem(); + String getDisplay(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/StorageResponseCodeEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/StorageResponseCodeEnum.java new file mode 100644 index 00000000000..27fde42c17e --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/StorageResponseCodeEnum.java @@ -0,0 +1,72 @@ +package ca.uhn.fhir.model.api; + +/*- + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2022 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% + */ + +/** + * This enum contains the allowable codes in the HAPI FHIR defined + * codesystem: https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code + * + * This is used in CRUD response OperationOutcome resources. + */ +public enum StorageResponseCodeEnum implements ICodingEnum { + + SUCCESSFUL_CREATE("Create succeeded."), + SUCCESSFUL_CREATE_NO_CONDITIONAL_MATCH("Conditional create succeeded: no existing resource matched the conditional URL."), + SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH("Conditional create succeeded: an existing resource matched the conditional URL so no action was taken."), + SUCCESSFUL_UPDATE("Update succeeded."), + SUCCESSFUL_UPDATE_AS_CREATE("Update as create succeeded."), + SUCCESSFUL_UPDATE_NO_CHANGE("Update succeeded: No changes were detected so no action was taken."), + SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH("Conditional update succeeded: no existing resource matched the conditional URL so a new resource was created."), + SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH("Conditional update succeeded: an existing resource matched the conditional URL and was updated."), + SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE("Conditional update succeeded: an existing resource matched the conditional URL and was updated, but no changes were detected so no action was taken."), + SUCCESSFUL_DELETE("Delete succeeded."), + SUCCESSFUL_DELETE_ALREADY_DELETED("Delete succeeded: Resource was already deleted so no action was taken."), + SUCCESSFUL_DELETE_NOT_FOUND("Delete succeeded: No existing resource was found so no action was taken."), + + SUCCESSFUL_PATCH("Patch succeeded."), + + SUCCESSFUL_PATCH_NO_CHANGE("Patch succeeded: No changes were detected so no action was taken."), + SUCCESSFUL_CONDITIONAL_PATCH("Conditional patch succeeded."), + SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE("Conditional patch succeeded: No changes were detected so no action was taken."); + + public static final String SYSTEM = "https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code"; + + private final String myDisplay; + + StorageResponseCodeEnum(String theDisplay) { + myDisplay = theDisplay; + } + + @Override + public String getCode() { + return name(); + } + + @Override + public String getSystem() { + return SYSTEM; + } + + @Override + public String getDisplay() { + return myDisplay; + } +} 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 9f531df5b35..7b3b3f52734 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 @@ -29,9 +29,12 @@ import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBackboneElement; import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import javax.annotation.Nonnull; import java.util.Objects; /** @@ -40,7 +43,7 @@ import java.util.Objects; * (method and search). * *

    - * + *

    * This is not yet complete, and doesn't support all FHIR features. USE WITH CAUTION as the API * may change. * @@ -101,10 +104,8 @@ public class BundleBuilder { /** * Sets the specified primitive field on the bundle with the value provided. * - * @param theFieldName - * Name of the primitive field. - * @param theFieldValue - * Value of the field to be set. + * @param theFieldName Name of the primitive field. + * @param theFieldValue Value of the field to be set. */ public BundleBuilder setBundleField(String theFieldName, String theFieldValue) { BaseRuntimeChildDefinition typeChild = myBundleDef.getChildByName(theFieldName); @@ -119,12 +120,9 @@ public class BundleBuilder { /** * Sets the specified primitive field on the search entry with the value provided. * - * @param theSearch - * Search part of the entry - * @param theFieldName - * Name of the primitive field. - * @param theFieldValue - * Value of the field to be set. + * @param theSearch Search part of the entry + * @param theFieldName Name of the primitive field. + * @param theFieldValue Value of the field to be set. */ public BundleBuilder setSearchField(IBase theSearch, String theFieldName, String theFieldValue) { BaseRuntimeChildDefinition typeChild = mySearchDef.getChildByName(theFieldName); @@ -144,6 +142,37 @@ public class BundleBuilder { return this; } + /** + * Adds a FHIRPatch patch bundle to the transaction + * @param theTarget The target resource ID to patch + * @param thePatch The FHIRPath Parameters resource + * @since 6.3.0 + */ + public PatchBuilder addTransactionFhirPatchEntry(IIdType theTarget, IBaseParameters thePatch) { + Validate.notNull(theTarget, "theTarget must not be null"); + Validate.notBlank(theTarget.getResourceType(), "theTarget must contain a resource type"); + Validate.notBlank(theTarget.getIdPart(), "theTarget must contain an ID"); + + IPrimitiveType url = addAndPopulateTransactionBundleEntryRequest(thePatch, theTarget.getValue(), theTarget.toUnqualifiedVersionless().getValue(), "PATCH"); + + return new PatchBuilder(url); + } + + /** + * Adds a FHIRPatch patch bundle to the transaction. This method is intended for conditional PATCH operations. If you + * know the ID of the resource you wish to patch, use {@link #addTransactionFhirPatchEntry(IIdType, IBaseParameters)} + * instead. + * + * @param thePatch The FHIRPath Parameters resource + * @since 6.3.0 + * @see #addTransactionFhirPatchEntry(IIdType, IBaseParameters) + */ + public PatchBuilder addTransactionFhirPatchEntry(IBaseParameters thePatch) { + IPrimitiveType url = addAndPopulateTransactionBundleEntryRequest(thePatch, null, null, "PATCH"); + + return new PatchBuilder(url); + } + /** * Adds an entry containing an update (PUT) request. * Also sets the Bundle.type value to "transaction" if it is not already set. @@ -151,22 +180,39 @@ public class BundleBuilder { * @param theResource The resource to update */ public UpdateBuilder addTransactionUpdateEntry(IBaseResource theResource) { + Validate.notNull(theResource, "theResource must not be null"); + + IIdType id = theResource.getIdElement(); + if (id.hasIdPart() && !id.hasResourceType()) { + String resourceType = myContext.getResourceType(theResource); + id = id.withResourceType(resourceType); + } + + String requestUrl = id.toUnqualifiedVersionless().getValue(); + String fullUrl = id.getValue(); + String verb = "PUT"; + + IPrimitiveType url = addAndPopulateTransactionBundleEntryRequest(theResource, fullUrl, requestUrl, verb); + + return new UpdateBuilder(url); + } + + @Nonnull + private IPrimitiveType addAndPopulateTransactionBundleEntryRequest(IBaseResource theResource, String theFullUrl, String theRequestUrl, String theHttpVerb) { setBundleField("type", "transaction"); - IBase request = addEntryAndReturnRequest(theResource); + IBase request = addEntryAndReturnRequest(theResource, theFullUrl); // Bundle.entry.request.url IPrimitiveType url = (IPrimitiveType) myContext.getElementDefinition("uri").newInstance(); - String resourceType = myContext.getResourceType(theResource); - url.setValueAsString(theResource.getIdElement().toUnqualifiedVersionless().withResourceType(resourceType).getValue()); + url.setValueAsString(theRequestUrl); myEntryRequestUrlChild.getMutator().setValue(request, url); - // Bundle.entry.request.url + // Bundle.entry.request.method IPrimitiveType method = (IPrimitiveType) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments()); - method.setValueAsString("PUT"); + method.setValueAsString(theHttpVerb); myEntryRequestMethodChild.getMutator().setValue(request, method); - - return new UpdateBuilder(url); + return url; } /** @@ -178,7 +224,7 @@ public class BundleBuilder { public CreateBuilder addTransactionCreateEntry(IBaseResource theResource) { setBundleField("type", "transaction"); - IBase request = addEntryAndReturnRequest(theResource); + IBase request = addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue()); String resourceType = myContext.getResourceType(theResource); @@ -198,15 +244,30 @@ public class BundleBuilder { /** * Adds an entry containing a delete (DELETE) request. * Also sets the Bundle.type value to "transaction" if it is not already set. - * + *

    * Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry, * * @param theResource The resource to delete. */ - public void addTransactionDeleteEntry(IBaseResource theResource) { + public DeleteBuilder addTransactionDeleteEntry(IBaseResource theResource) { String resourceType = myContext.getResourceType(theResource); String idPart = theResource.getIdElement().toUnqualifiedVersionless().getIdPart(); - addTransactionDeleteEntry(resourceType, idPart); + return addTransactionDeleteEntry(resourceType, idPart); + } + + /** + * Adds an entry containing a delete (DELETE) request. + * Also sets the Bundle.type value to "transaction" if it is not already set. + *

    + * Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry, + * + * @param theResourceId The resource ID to delete. + * @return + */ + public DeleteBuilder addTransactionDeleteEntry(IIdType theResourceId) { + String resourceType = theResourceId.getResourceType(); + String idPart = theResourceId.getIdPart(); + return addTransactionDeleteEntry(resourceType, idPart); } /** @@ -214,24 +275,45 @@ public class BundleBuilder { * Also sets the Bundle.type value to "transaction" if it is not already set. * * @param theResourceType The type resource to delete. - * @param theIdPart the ID of the resource to delete. + * @param theIdPart the ID of the resource to delete. */ - public void addTransactionDeleteEntry(String theResourceType, String theIdPart) { + public DeleteBuilder addTransactionDeleteEntry(String theResourceType, String theIdPart) { setBundleField("type", "transaction"); - IBase request = addEntryAndReturnRequest(); IdDt idDt = new IdDt(theIdPart); - + + String deleteUrl = idDt.toUnqualifiedVersionless().withResourceType(theResourceType).getValue(); + + return addDeleteEntry(deleteUrl); + } + + /** + * Adds an entry containing a delete (DELETE) request. + * Also sets the Bundle.type value to "transaction" if it is not already set. + * + * @param theMatchUrl The match URL, e.g. Patient?identifier=http://foo|123 + * @since 6.3.0 + */ + public BaseOperationBuilder addTransactionDeleteEntryConditional(String theMatchUrl) { + Validate.notBlank(theMatchUrl, "theMatchUrl must not be null or blank"); + return addDeleteEntry(theMatchUrl); + } + + @Nonnull + private DeleteBuilder addDeleteEntry(String theDeleteUrl) { + IBase request = addEntryAndReturnRequest(); + // Bundle.entry.request.url IPrimitiveType url = (IPrimitiveType) myContext.getElementDefinition("uri").newInstance(); - url.setValueAsString(idDt.toUnqualifiedVersionless().withResourceType(theResourceType).getValue()); + url.setValueAsString(theDeleteUrl); myEntryRequestUrlChild.getMutator().setValue(request, url); // Bundle.entry.request.method IPrimitiveType method = (IPrimitiveType) myEntryRequestMethodDef.newInstance(myEntryRequestMethodChild.getInstanceConstructorArguments()); method.setValueAsString("DELETE"); myEntryRequestMethodChild.getMutator().setValue(request, method); - } + return new DeleteBuilder(); + } /** @@ -239,14 +321,13 @@ public class BundleBuilder { */ public void addCollectionEntry(IBaseResource theResource) { setType("collection"); - addEntryAndReturnRequest(theResource); + addEntryAndReturnRequest(theResource, theResource.getIdElement().getValue()); } /** * Creates new entry and adds it to the bundle * - * @return - * Returns the new entry. + * @return Returns the new entry. */ public IBase addEntry() { IBase entry = myEntryDef.newInstance(); @@ -258,8 +339,7 @@ public class BundleBuilder { * Creates new search instance for the specified entry * * @param entry Entry to create search instance for - * @return - * Returns the search instance + * @return Returns the search instance */ public IBaseBackboneElement addSearch(IBase entry) { IBase searchInstance = mySearchDef.newInstance(); @@ -267,19 +347,14 @@ public class BundleBuilder { return (IBaseBackboneElement) searchInstance; } - /** - * - * @param theResource - * @return - */ - public IBase addEntryAndReturnRequest(IBaseResource theResource) { + private IBase addEntryAndReturnRequest(IBaseResource theResource, String theFullUrl) { Validate.notNull(theResource, "theResource must not be null"); IBase entry = addEntry(); // Bundle.entry.fullUrl IPrimitiveType fullUrl = (IPrimitiveType) myContext.getElementDefinition("uri").newInstance(); - fullUrl.setValueAsString(theResource.getIdElement().getValue()); + fullUrl.setValueAsString(theFullUrl); myEntryFullUrlChild.getMutator().setValue(entry, fullUrl); // Bundle.entry.resource @@ -306,6 +381,15 @@ public class BundleBuilder { return myBundle; } + /** + * Convenience method which auto-casts the results of {@link #getBundle()} + * + * @since 6.3.0 + */ + public T getBundleTyped() { + return (T) myBundle; + } + public BundleBuilder setMetaField(String theFieldName, IBase theFieldValue) { BaseRuntimeChildDefinition.IMutator mutator = myMetaDef.getChildByName(theFieldName).getMutator(); mutator.setValue(myBundle.getMeta(), theFieldValue); @@ -315,12 +399,9 @@ public class BundleBuilder { /** * Sets the specified entry field. * - * @param theEntry - * The entry instance to set values on - * @param theEntryChildName - * The child field name of the entry instance to be set - * @param theValue - * The field value to set + * @param theEntry The entry instance to set values on + * @param theEntryChildName The child field name of the entry instance to be set + * @param theValue The field value to set */ public void addToEntry(IBase theEntry, String theEntryChildName, IBase theValue) { addToBase(theEntry, theEntryChildName, theValue, myEntryDef); @@ -329,12 +410,9 @@ public class BundleBuilder { /** * Sets the specified search field. * - * @param theSearch - * The search instance to set values on - * @param theSearchFieldName - * The child field name of the search instance to be set - * @param theSearchFieldValue - * The field value to set + * @param theSearch The search instance to set values on + * @param theSearchFieldName The child field name of the search instance to be set + * @param theSearchFieldValue The field value to set */ public void addToSearch(IBase theSearch, String theSearchFieldName, IBase theSearchFieldValue) { addToBase(theSearch, theSearchFieldName, theSearchFieldValue, mySearchDef); @@ -349,12 +427,9 @@ public class BundleBuilder { /** * Creates a new primitive. * - * @param theTypeName - * The element type for the primitive - * @param - * Actual type of the parameterized primitive type interface - * @return - * Returns the new empty instance of the element definition. + * @param theTypeName The element type for the primitive + * @param Actual type of the parameterized primitive type interface + * @return Returns the new empty instance of the element definition. */ public IPrimitiveType newPrimitive(String theTypeName) { BaseRuntimeElementDefinition primitiveDefinition = myContext.getElementDefinition(theTypeName); @@ -365,14 +440,10 @@ public class BundleBuilder { /** * Creates a new primitive instance of the specified element type. * - * @param theTypeName - * Element type to create - * @param theInitialValue - * Initial value to be set on the new instance - * @param - * Actual type of the parameterized primitive type interface - * @return - * Returns the newly created instance + * @param theTypeName Element type to create + * @param theInitialValue Initial value to be set on the new instance + * @param Actual type of the parameterized primitive type interface + * @return Returns the newly created instance */ public IPrimitiveType newPrimitive(String theTypeName, T theInitialValue) { IPrimitiveType retVal = newPrimitive(theTypeName); @@ -389,38 +460,84 @@ public class BundleBuilder { setBundleField("type", theType); } - public static class UpdateBuilder { - private final IPrimitiveType myUrl; + public class DeleteBuilder extends BaseOperationBuilder { - public UpdateBuilder(IPrimitiveType theUrl) { - myUrl = theUrl; - } + // nothing yet - /** - * Make this update a Conditional Update - */ - public void conditional(String theConditionalUrl) { - myUrl.setValueAsString(theConditionalUrl); - } } - public class CreateBuilder { + + public class PatchBuilder extends BaseOperationBuilderWithConditionalUrl { + + PatchBuilder(IPrimitiveType theUrl) { + super(theUrl); + } + + } + + public class UpdateBuilder extends BaseOperationBuilderWithConditionalUrl { + UpdateBuilder(IPrimitiveType theUrl) { + super(theUrl); + } + + } + + public class CreateBuilder extends BaseOperationBuilder { private final IBase myRequest; - public CreateBuilder(IBase theRequest) { + CreateBuilder(IBase theRequest) { myRequest = theRequest; } /** * Make this create a Conditional Create */ - public void conditional(String theConditionalUrl) { + public CreateBuilder conditional(String theConditionalUrl) { BaseRuntimeElementDefinition stringDefinition = Objects.requireNonNull(myContext.getElementDefinition("string")); IPrimitiveType ifNoneExist = (IPrimitiveType) stringDefinition.newInstance(); ifNoneExist.setValueAsString(theConditionalUrl); myEntryRequestIfNoneExistChild.getMutator().setValue(myRequest, ifNoneExist); + + return this; + } + + } + + public abstract class BaseOperationBuilder { + + /** + * Returns a reference to the BundleBuilder instance. + * + * Calling this method has no effect at all, it is only + * provided for easy method chaning if you want to build + * your bundle as a single fluent call. + * + * @since 6.3.0 + */ + public BundleBuilder andThen() { + return BundleBuilder.this; + } + + + } + + public abstract class BaseOperationBuilderWithConditionalUrl extends BaseOperationBuilder { + + private final IPrimitiveType myUrl; + + BaseOperationBuilderWithConditionalUrl(IPrimitiveType theUrl) { + myUrl = theUrl; + } + + /** + * Make this update a Conditional Update + */ + @SuppressWarnings("unchecked") + public T conditional(String theConditionalUrl) { + myUrl.setValueAsString(theConditionalUrl); + return (T) this; } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/OperationOutcomeUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/OperationOutcomeUtil.java index 06be9374ef8..3c03babc505 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/OperationOutcomeUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/OperationOutcomeUtil.java @@ -29,11 +29,13 @@ import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.ICompositeType; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import javax.annotation.Nullable; import java.util.List; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -53,8 +55,12 @@ public class OperationOutcomeUtil { * @return Returns the newly added issue */ public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode) { + return addIssue(theCtx, theOperationOutcome, theSeverity, theDetails, theLocation, theCode, null, null, null); + } + + public static IBase addIssue(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, String theSeverity, String theDetails, String theLocation, String theCode, @Nullable String theDetailSystem, @Nullable String theDetailCode, @Nullable String theDetailDescription) { IBase issue = createIssue(theCtx, theOperationOutcome); - populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode); + populateDetails(theCtx, issue, theSeverity, theDetails, theLocation, theCode, theDetailSystem, theDetailCode, theDetailDescription); return issue; } @@ -127,17 +133,17 @@ public class OperationOutcomeUtil { } } - private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode) { + private static void populateDetails(FhirContext theCtx, IBase theIssue, String theSeverity, String theDetails, String theLocation, String theCode, String theDetailSystem, String theDetailCode, String theDetailDescription) { BaseRuntimeElementCompositeDefinition issueElement = (BaseRuntimeElementCompositeDefinition) theCtx.getElementDefinition(theIssue.getClass()); - BaseRuntimeChildDefinition detailsChild; - detailsChild = issueElement.getChildByName("diagnostics"); + BaseRuntimeChildDefinition diagnosticsChild; + diagnosticsChild = issueElement.getChildByName("diagnostics"); BaseRuntimeChildDefinition codeChild = issueElement.getChildByName("code"); IPrimitiveType codeElem = (IPrimitiveType) codeChild.getChildByName("code").newInstance(codeChild.getInstanceConstructorArguments()); codeElem.setValueAsString(theCode); codeChild.getMutator().addValue(theIssue, codeElem); - BaseRuntimeElementDefinition stringDef = detailsChild.getChildByName(detailsChild.getElementName()); + BaseRuntimeElementDefinition stringDef = diagnosticsChild.getChildByName(diagnosticsChild.getElementName()); BaseRuntimeChildDefinition severityChild = issueElement.getChildByName("severity"); IPrimitiveType severityElem = (IPrimitiveType) severityChild.getChildByName("severity").newInstance(severityChild.getInstanceConstructorArguments()); @@ -146,9 +152,27 @@ public class OperationOutcomeUtil { IPrimitiveType string = (IPrimitiveType) stringDef.newInstance(); string.setValueAsString(theDetails); - detailsChild.getMutator().setValue(theIssue, string); + diagnosticsChild.getMutator().setValue(theIssue, string); addLocationToIssue(theCtx, theIssue, theLocation); + + if (isNotBlank(theDetailSystem)) { + BaseRuntimeChildDefinition detailsChild = issueElement.getChildByName("details"); + if (detailsChild != null) { + BaseRuntimeElementDefinition codeableConceptDef = theCtx.getElementDefinition("CodeableConcept"); + IBase codeableConcept = codeableConceptDef.newInstance(); + + BaseRuntimeElementDefinition codingDef = theCtx.getElementDefinition("Coding"); + IBaseCoding coding = (IBaseCoding) codingDef.newInstance(); + coding.setSystem(theDetailSystem); + coding.setCode(theDetailCode); + coding.setDisplay(theDetailDescription); + + codeableConceptDef.getChildByName("coding").getMutator().addValue(codeableConcept, coding); + + detailsChild.getMutator().addValue(theIssue, codeableConcept); + } + } } public static void addLocationToIssue(FhirContext theContext, IBase theIssue, String theLocation) { diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index fa7558e5fb0..966e3fef2ad 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -99,10 +99,22 @@ ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidParameterChain=Invalid parameter chain ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidVersion=Version "{0}" is not valid for resource {1} ca.uhn.fhir.jpa.dao.BaseStorageDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true ca.uhn.fhir.jpa.dao.BaseStorageDao.missingBody=No body was supplied in request -ca.uhn.fhir.jpa.dao.BaseStorageDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed. -ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreate=Successfully created resource "{0}" in {1}ms -ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms -ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulDeletes=Successfully deleted {0} resource(s) in {1}ms +ca.uhn.fhir.jpa.dao.BaseStorageDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Nothing has been deleted. +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreate=Successfully created resource "{0}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreateConditionalNoMatch=Successfully conditionally created resource "{0}". No existing resources matched URL "{1}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulCreateConditionalWithMatch=Successfully conditionally created resource "{0}". Existing resource matched URL "{1}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatch=Successfully patched resource "{0}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchNoChange=Successfully patched resource "{0}" with no changes detected. +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchConditional=Successfully conditionally patched resource. Existing resource {0} matched URL: {1}. +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulPatchConditionalNoChange=Successfully conditionally patched resource with no changes detected. Existing resource {0} matched URL: {1}. +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdate=Successfully updated resource "{0}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateNoChange=Successfully updated resource "{0}" with no changes detected. +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateAsCreate=Successfully created resource "{0}" using update as create (ie. create with client assigned ID). +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalWithMatch=Successfully conditionally updated resource "{0}". Existing resource matched URL "{1}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalNoMatch=Successfully conditionally updated resource "{0}". Created resource because no existing resource matched URL "{1}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulUpdateConditionalNoChangeWithMatch=Successfully conditionally updated resource "{0}" with no changes detected. Existing resource matched URL "{1}". +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulDeletes=Successfully deleted {0} resource(s). +ca.uhn.fhir.jpa.dao.BaseStorageDao.successfulTimingSuffix=Took {0}ms. ca.uhn.fhir.jpa.dao.BaseStorageDao.deleteResourceNotExisting=Not deleted, resource {0} does not exist. ca.uhn.fhir.jpa.dao.BaseStorageDao.deleteResourceAlreadyDeleted=Not deleted, resource {0} was already deleted. ca.uhn.fhir.jpa.dao.BaseStorageDao.invalidSearchParameter=Unknown search parameter "{0}" for resource type "{1}". Valid search parameters for this search are: {2} diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index 56b5d5fd7c0..825e4431039 100644 --- a/hapi-fhir-bom/pom.xml +++ b/hapi-fhir-bom/pom.xml @@ -3,14 +3,14 @@ 4.0.0 ca.uhn.hapi.fhir hapi-fhir-bom - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT pom HAPI FHIR BOM ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml index a9a5cecf5e8..b986d1077f1 100644 --- a/hapi-fhir-checkstyle/pom.xml +++ b/hapi-fhir-checkstyle/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-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 b7730bbc885..70f2caf3980 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.3.1-SNAPSHOT + 6.3.2-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 0ed4c30293e..62b42c5e86b 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml index 6a108f56b8b..1c624dadc7c 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml +++ b/hapi-fhir-cli/hapi-fhir-cli-jpaserver/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../../hapi-deployable-pom diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index efc6dd0427f..1a3e2fe756d 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index c8ba145dea2..590d627faab 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index 599deda15a2..60c6713c747 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index a173b06c4c7..ebb58ce95dc 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index e30f11dc432..f88a1cd4aa9 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index f9b1202c936..adffe9d0272 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java index 46998c941f0..39d9a3a3495 100644 --- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java +++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java @@ -26,6 +26,11 @@ import ca.uhn.fhir.util.BundleBuilder; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.BooleanType; +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 java.math.BigDecimal; @@ -109,6 +114,53 @@ public class BundleBuilderExamples { //END SNIPPET: createConditional } + public void patch() throws FHIRException { + //START SNIPPET: patch + + // Create a FHIR Patch object + Parameters patch = new Parameters(); + Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation"); + op.addPart().setName("type").setValue(new CodeType("replace")); + op.addPart().setName("path").setValue(new CodeType("Patient.active")); + op.addPart().setName("value").setValue(new BooleanType(false)); + + // Create a TransactionBuilder + BundleBuilder builder = new BundleBuilder(myFhirContext); + + // Create a target object (this is the ID of the resource that will be patched) + IIdType targetId = new IdType("Patient/123"); + + // Add the patch to the bundle + builder.addTransactionFhirPatchEntry(targetId, patch); + + // Execute the transaction + IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute(); + //END SNIPPET: patch + } + + public void patchConditional() throws FHIRException { + //START SNIPPET: patchConditional + + // Create a FHIR Patch object + Parameters patch = new Parameters(); + Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation"); + op.addPart().setName("type").setValue(new CodeType("replace")); + op.addPart().setName("path").setValue(new CodeType("Patient.active")); + op.addPart().setName("value").setValue(new BooleanType(false)); + + // Create a TransactionBuilder + BundleBuilder builder = new BundleBuilder(myFhirContext); + + // Add the patch to the bundle with a conditional URL + String conditionalUrl = "Patient?identifier=http://foo|123"; + builder.addTransactionFhirPatchEntry(patch).conditional(conditionalUrl); + + + // Execute the transaction + IBaseBundle outcome = myFhirClient.transaction().withBundle(builder.getBundle()).execute(); + //END SNIPPET: patchConditional + } + public void customizeBundle() throws FHIRException { //START SNIPPET: customizeBundle // Create a TransactionBuilder diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-add-conditional-delete-to-bundlebuilder.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-add-conditional-delete-to-bundlebuilder.yaml new file mode 100644 index 00000000000..bf4907aab9b --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-add-conditional-delete-to-bundlebuilder.yaml @@ -0,0 +1,6 @@ +--- +type: add +issue: 4293 +title: "The BundleBuilder now supports adding conditional + DELETE operations, PATCH operations, and conditional PATCH + operations to a transaction bundle." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-improved-operationoutcome-for-jpa-cud.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-improved-operationoutcome-for-jpa-cud.yaml new file mode 100644 index 00000000000..38ff5ad4a91 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-improved-operationoutcome-for-jpa-cud.yaml @@ -0,0 +1,11 @@ +--- +type: add +issue: 4293 +title: "When performing create/update/patch/delete operations against the JPA server, the response + OperationOutcome will now include additional details about the outcome of the operation. This + includes: +

      +
    • For updates, the message will indicate the the update did not contain any changes (i.e. a No-op)
    • +
    • For conditional creates/updates/deletes, the message will indicate whether the conditional URL matched any existing resources and the outcome of the operation.
    • +
    • A new coding has been added to the OperationOutcome.issue.details.coding containing a machine processable equivalent to the outcome.
    • +
    " diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-include-carried-tags-in-transaction-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-include-carried-tags-in-transaction-response.yaml new file mode 100644 index 00000000000..469d71838b5 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_3_0/4293-include-carried-tags-in-transaction-response.yaml @@ -0,0 +1,7 @@ +--- +type: add +issue: 4293 +title: "When updating resources using a FHIR transaction in the JPA server, if the + client instructs the server to include the resource body in the response, any + tags that have been carried forward from previous versions of the resource are + now included in the response." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/bundle_builder.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/bundle_builder.md index 675c6308c3e..40156f9f4ce 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/bundle_builder.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/bundle_builder.md @@ -36,7 +36,23 @@ If you want to perform a conditional update: {{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|updateConditional}} ``` -# Customizing bundle +# Transaction Patch + +To add a PATCH operation to a transaction bundle: + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|patch}} +``` + +## Conditional Patch + +If you want to perform a conditional patch: + +```java +{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleBuilderExamples.java|patchConditional}} +``` + +# Customizing the Bundle If you want to manipulate a bundle: diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index e839b35abb7..b5add09df5c 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 3cbf7c8c81d..2e20067fa7f 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml index 367298467b4..19277e66e95 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/SqlQuery.java b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/SqlQuery.java index c0cd943a143..90f5f512974 100644 --- a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/SqlQuery.java +++ b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/SqlQuery.java @@ -39,14 +39,16 @@ public class SqlQuery { private final StackTraceElement[] myStackTrace; private final int mySize; private final LanguageEnum myLanguage; + private final String myNamespace; public SqlQuery(String theSql, List theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize) { - this(theSql, theParams, theQueryTimestamp, theElapsedTime, theStackTraceElements, theSize, LanguageEnum.SQL); + this(null, theSql, theParams, theQueryTimestamp, theElapsedTime, theStackTraceElements, theSize, LanguageEnum.SQL); } - public SqlQuery(String theSql, List theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize, LanguageEnum theLanguage) { + public SqlQuery(String theNamespace, String theSql, List theParams, long theQueryTimestamp, long theElapsedTime, StackTraceElement[] theStackTraceElements, int theSize, LanguageEnum theLanguage) { Validate.notNull(theLanguage, "theLanguage must not be null"); + myNamespace = theNamespace; mySql = theSql; myParams = Collections.unmodifiableList(theParams); myQueryTimestamp = theQueryTimestamp; @@ -56,6 +58,10 @@ public class SqlQuery { myLanguage = theLanguage; } + public String getNamespace() { + return myNamespace; + } + public long getQueryTimestamp() { return myQueryTimestamp; } @@ -118,6 +124,10 @@ public class SqlQuery { return mySize; } + @Override + public String toString() { + return getSql(true, true); + } public enum LanguageEnum { @@ -125,9 +135,4 @@ public class SqlQuery { JSON } - - @Override - public String toString() { - return getSql(true, true); - } } diff --git a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java index 371728b9f6d..5ad9410b772 100644 --- a/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java +++ b/hapi-fhir-jpa/src/main/java/ca/uhn/fhir/jpa/util/TestUtil.java @@ -384,7 +384,7 @@ public class TestUtil { } public static void sleepOneClick() { - ca.uhn.fhir.util.TestUtil.sleepAtLeast(1); + ca.uhn.fhir.util.TestUtil.sleepAtLeast(1, false); } diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 41eacea7e0f..2fff7a3da69 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/Batch2SupportConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/Batch2SupportConfig.java index 9b1b5f64af6..de8adf4362c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/Batch2SupportConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/Batch2SupportConfig.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.jpa.dao.index.IJpaIdHelperService; import ca.uhn.fhir.jpa.delete.batch2.DeleteExpungeSqlBuilder; import ca.uhn.fhir.jpa.delete.batch2.DeleteExpungeSvcImpl; import ca.uhn.fhir.jpa.reindex.Batch2DaoSvcImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import javax.persistence.EntityManager; @@ -43,7 +44,7 @@ public class Batch2SupportConfig { } @Bean - public IDeleteExpungeSvc deleteExpungeSvc(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, IFulltextSearchSvc theFullTextSearchSvc) { + public IDeleteExpungeSvc deleteExpungeSvc(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, @Autowired(required = false) IFulltextSearchSvc theFullTextSearchSvc) { return new DeleteExpungeSvcImpl(theEntityManager, theDeleteExpungeSqlBuilder, theFullTextSearchSvc); } 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 207f69f94a5..853f2ae507e 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 @@ -25,7 +25,9 @@ import ca.uhn.fhir.jpa.dao.DaoSearchParamProvider; import ca.uhn.fhir.jpa.dao.HistoryBuilder; import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; 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.ObservationLastNIndexPersistSvc; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; @@ -230,7 +232,6 @@ public class JpaConfig { return new ResponseTerminologyTranslationInterceptor(theValidationSupport, theResponseTerminologyTranslationSvc); } - @Lazy @Bean public ResponseTerminologyTranslationSvc responseTerminologyTranslationSvc(IValidationSupport theValidationSupport) { return new ResponseTerminologyTranslationSvc(theValidationSupport); @@ -265,6 +266,11 @@ public class JpaConfig { return new ValueSetOperationProvider(); } + @Bean + public IJpaStorageResourceParser jpaStorageResourceParser() { + return new JpaStorageResourceParser(); + } + @Bean public TransactionProcessor transactionProcessor() { return new TransactionProcessor(); 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 4d307a949dd..7dd6a83f366 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 @@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IDao; import ca.uhn.fhir.jpa.api.dao.IJpaDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.api.svc.IIdHelperService; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; @@ -29,8 +30,6 @@ import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; import ca.uhn.fhir.jpa.delete.DeleteConflictService; import ca.uhn.fhir.jpa.entity.PartitionEntity; import ca.uhn.fhir.jpa.entity.ResourceSearchView; -import ca.uhn.fhir.jpa.entity.Search; -import ca.uhn.fhir.jpa.entity.SearchTypeEnum; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; @@ -45,11 +44,9 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTag; import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; import ca.uhn.fhir.jpa.model.search.ExtendedHSearchIndexData; -import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; -import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory; import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper; import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams; import ca.uhn.fhir.jpa.searchparam.matcher.InMemoryMatchResult; @@ -60,6 +57,7 @@ import ca.uhn.fhir.jpa.util.AddRemoveCount; import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; import ca.uhn.fhir.model.base.composite.BaseCodingDt; @@ -71,14 +69,12 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum; -import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; -import ca.uhn.fhir.rest.param.HistorySearchStyleEnum; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; @@ -86,6 +82,8 @@ import ca.uhn.fhir.rest.server.util.ResourceSearchParams; import ca.uhn.fhir.util.CoverageIgnore; import ca.uhn.fhir.util.HapiExtensions; import ca.uhn.fhir.util.MetaUtil; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.XmlUtil; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; @@ -148,11 +146,9 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; -import java.util.UUID; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.model.util.JpaConstants.ALL_PARTITIONS_NAME; -import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -181,9 +177,18 @@ import static org.apache.commons.lang3.StringUtils.trim; * #L% */ + +/** + * TODO: JA - This class has only one subclass now. Historically it was a common + * ancestor for BaseHapiFhirSystemDao and BaseHapiFhirResourceDao but I've untangled + * the former from this hierarchy in order to simplify moving common functionality + * for resource DAOs into the hapi-fhir-storage project. This class should be merged + * into BaseHapiFhirResourceDao, but that should be done in its own dedicated PR + * since it'll be a noisy change. + */ @SuppressWarnings("WeakerAccess") @Repository -public abstract class BaseHapiFhirDao extends BaseStorageDao implements IDao, IJpaDao, ApplicationContextAware { +public abstract class BaseHapiFhirDao extends BaseStorageResourceDao implements IDao, IJpaDao, ApplicationContextAware { public static final long INDEX_STATUS_INDEXED = 1L; public static final long INDEX_STATUS_INDEXING_FAILED = 2L; @@ -233,8 +238,6 @@ public abstract class BaseHapiFhirDao extends BaseStora @Autowired private PartitionSettings myPartitionSettings; @Autowired - private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; - @Autowired private IPartitionLookupSvc myPartitionLookupSvc; @Autowired private MemoryCacheService myMemoryCacheService; @@ -243,6 +246,8 @@ public abstract class BaseHapiFhirDao extends BaseStora @Autowired private PlatformTransactionManager myTransactionManager; + @Autowired + protected IJpaStorageResourceParser myJpaStorageResourceParser; @VisibleForTesting public void setSearchParamPresenceSvc(ISearchParamPresenceSvc theSearchParamPresenceSvc) { @@ -371,14 +376,6 @@ public abstract class BaseHapiFhirDao extends BaseStora myContext = theContext; } - public FhirContext getContext(FhirVersionEnum theVersion) { - Validate.notNull(theVersion, "theVersion must not be null"); - if (theVersion == myFhirContext.getVersion().getVersion()) { - return myFhirContext; - } - return FhirContext.forCached(theVersion); - } - /** * null will only be returned if the scheme and tag are both blank */ @@ -513,27 +510,6 @@ public abstract class BaseHapiFhirDao extends BaseStora return retVal; } - protected IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset) { - return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null); - } - - protected IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType) { - String resourceName = defaultIfBlank(theResourceType, null); - - Search search = new Search(); - search.setOffset(theOffset); - search.setDeleted(false); - search.setCreated(new Date()); - search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive); - search.setUuid(UUID.randomUUID().toString()); - search.setResourceType(resourceName); - search.setResourceId(theResourcePid); - search.setSearchType(SearchTypeEnum.HISTORY); - search.setStatus(SearchStatusEnum.FINISHED); - search.setHistorySearchStyle(searchParameterType); - - return myPersistedJpaBundleProviderFactory.newInstance(theRequest, search); - } void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) { String newVersion; @@ -796,133 +772,6 @@ public abstract class BaseHapiFhirDao extends BaseStora return !allTagsOld.equals(allTagsNew); } - @SuppressWarnings("unchecked") - private R populateResourceMetadataHapi(Class theResourceType, IBaseResourceEntity theEntity, @Nullable Collection theTagList, boolean theForHistoryOperation, IResource res, Long theVersion) { - R retVal = (R) res; - if (theEntity.getDeleted() != null) { - res = (IResource) myContext.getResourceDefinition(theResourceType).newInstance(); - retVal = (R) res; - ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); - if (theForHistoryOperation) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); - } - } else if (theForHistoryOperation) { - /* - * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT. - */ - Date published = theEntity.getPublished().getValue(); - Date updated = theEntity.getUpdated().getValue(); - if (published.equals(updated)) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); - } else { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); - } - } - - res.setId(theEntity.getIdDt().withVersion(theVersion.toString())); - - ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion())); - ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished()); - ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated()); - IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); - - if (theTagList != null) { - if (theEntity.isHasTags()) { - TagList tagList = new TagList(); - List securityLabels = new ArrayList<>(); - List profiles = new ArrayList<>(); - for (BaseTag next : theTagList) { - switch (next.getTag().getTagType()) { - case PROFILE: - profiles.add(new IdDt(next.getTag().getCode())); - break; - case SECURITY_LABEL: - IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt(); - secLabel.setSystem(next.getTag().getSystem()); - secLabel.setCode(next.getTag().getCode()); - secLabel.setDisplay(next.getTag().getDisplay()); - securityLabels.add(secLabel); - break; - case TAG: - tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay())); - break; - } - } - if (tagList.size() > 0) { - ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList); - } - if (securityLabels.size() > 0) { - ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels)); - } - if (profiles.size() > 0) { - ResourceMetadataKeyEnum.PROFILES.put(res, profiles); - } - } - } - - return retVal; - } - - @SuppressWarnings("unchecked") - private R populateResourceMetadataRi(Class theResourceType, IBaseResourceEntity theEntity, @Nullable Collection theTagList, boolean theForHistoryOperation, IAnyResource res, Long theVersion) { - R retVal = (R) res; - if (theEntity.getDeleted() != null) { - res = (IAnyResource) myContext.getResourceDefinition(theResourceType).newInstance(); - retVal = (R) res; - ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); - if (theForHistoryOperation) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.DELETE.toCode()); - } - } else if (theForHistoryOperation) { - /* - * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT. - */ - Date published = theEntity.getPublished().getValue(); - Date updated = theEntity.getUpdated().getValue(); - if (published.equals(updated)) { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.POST.toCode()); - } else { - ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, HTTPVerb.PUT.toCode()); - } - } - - res.getMeta().setLastUpdated(null); - res.getMeta().setVersionId(null); - - updateResourceMetadata(theEntity, res); - res.setId(res.getIdElement().withVersion(theVersion.toString())); - - res.getMeta().setLastUpdated(theEntity.getUpdatedDate()); - IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); - - if (theTagList != null) { - res.getMeta().getTag().clear(); - res.getMeta().getProfile().clear(); - res.getMeta().getSecurity().clear(); - for (BaseTag next : theTagList) { - switch (next.getTag().getTagType()) { - case PROFILE: - res.getMeta().addProfile(next.getTag().getCode()); - break; - case SECURITY_LABEL: - IBaseCoding sec = res.getMeta().addSecurity(); - sec.setSystem(next.getTag().getSystem()); - sec.setCode(next.getTag().getCode()); - sec.setDisplay(next.getTag().getDisplay()); - break; - case TAG: - IBaseCoding tag = res.getMeta().addTag(); - tag.setSystem(next.getTag().getSystem()); - tag.setCode(next.getTag().getCode()); - tag.setDisplay(next.getTag().getDisplay()); - break; - } - } - } - - return retVal; - } - /** * Subclasses may override to provide behaviour. Called when a pre-existing resource has been updated in the database * @@ -954,6 +803,7 @@ public abstract class BaseHapiFhirDao extends BaseStora // nothing } + @Override @CoverageIgnore public BaseHasResource readEntity(IIdType theValueId, RequestDetails theRequest) { throw new NotImplementedException(Msg.code(927) + ""); @@ -1005,220 +855,8 @@ public abstract class BaseHapiFhirDao extends BaseStora return metaSnapshotModeTokens.contains(theTag.getTag().getTagType()); } - @Override - public IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation) { - RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType()); - Class resourceType = type.getImplementingClass(); - return toResource(resourceType, theEntity, null, theForHistoryOperation); - } - @SuppressWarnings("unchecked") - @Override - public R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation) { - // 1. get resource, it's encoding and the tags if any - byte[] resourceBytes; - String resourceText; - ResourceEncodingEnum resourceEncoding; - @Nullable - Collection tagList = Collections.emptyList(); - long version; - String provenanceSourceUri = null; - String provenanceRequestId = null; - - if (theEntity instanceof ResourceHistoryTable) { - ResourceHistoryTable history = (ResourceHistoryTable) theEntity; - resourceBytes = history.getResource(); - resourceText = history.getResourceTextVc(); - resourceEncoding = history.getEncoding(); - switch (getConfig().getTagStorageMode()) { - case VERSIONED: - default: - if (history.isHasTags()) { - tagList = history.getTags(); - } - break; - case NON_VERSIONED: - if (history.getResourceTable().isHasTags()) { - tagList = history.getResourceTable().getTags(); - } - break; - case INLINE: - tagList = null; - } - version = history.getVersion(); - if (history.getProvenance() != null) { - provenanceRequestId = history.getProvenance().getRequestId(); - provenanceSourceUri = history.getProvenance().getSourceUri(); - } - } else if (theEntity instanceof ResourceTable) { - ResourceTable resource = (ResourceTable) theEntity; - ResourceHistoryTable history; - if (resource.getCurrentVersionEntity() != null) { - history = resource.getCurrentVersionEntity(); - } else { - version = theEntity.getVersion(); - history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version); - ((ResourceTable) theEntity).setCurrentVersionEntity(history); - - while (history == null) { - if (version > 1L) { - version--; - history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version); - } else { - return null; - } - } - } - - resourceBytes = history.getResource(); - resourceEncoding = history.getEncoding(); - resourceText = history.getResourceTextVc(); - switch (getConfig().getTagStorageMode()) { - case VERSIONED: - case NON_VERSIONED: - if (resource.isHasTags()) { - tagList = resource.getTags(); - } else { - tagList = Collections.emptyList(); - } - break; - case INLINE: - tagList = null; - break; - } - version = history.getVersion(); - if (history.getProvenance() != null) { - provenanceRequestId = history.getProvenance().getRequestId(); - provenanceSourceUri = history.getProvenance().getSourceUri(); - } - } else if (theEntity instanceof ResourceSearchView) { - // This is the search View - ResourceSearchView view = (ResourceSearchView) theEntity; - resourceBytes = view.getResource(); - resourceText = view.getResourceTextVc(); - resourceEncoding = view.getEncoding(); - version = view.getVersion(); - provenanceRequestId = view.getProvenanceRequestId(); - provenanceSourceUri = view.getProvenanceSourceUri(); - switch (getConfig().getTagStorageMode()) { - case VERSIONED: - case NON_VERSIONED: - if (theTagList != null) { - tagList = theTagList; - } else { - tagList = Collections.emptyList(); - } - break; - case INLINE: - tagList = null; - break; - } - } else { - // something wrong - return null; - } - - // 2. get The text - String decodedResourceText; - if (resourceText != null) { - decodedResourceText = resourceText; - } else { - decodedResourceText = decodeResource(resourceBytes, resourceEncoding); - } - - // 3. Use the appropriate custom type if one is specified in the context - Class resourceType = theResourceType; - if (tagList != null) { - if (myContext.hasDefaultTypeForProfile()) { - for (BaseTag nextTag : tagList) { - if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) { - String profile = nextTag.getTag().getCode(); - if (isNotBlank(profile)) { - Class newType = myContext.getDefaultTypeForProfile(profile); - if (newType != null && theResourceType.isAssignableFrom(newType)) { - ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile); - resourceType = (Class) newType; - break; - } - } - } - } - } - } - - // 4. parse the text to FHIR - R retVal; - if (resourceEncoding != ResourceEncodingEnum.DEL) { - - LenientErrorHandler errorHandler = new LenientErrorHandler(false).setErrorOnInvalidValue(false); - IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), errorHandler, theEntity.getId()); - - try { - retVal = parser.parseResource(resourceType, decodedResourceText); - } catch (Exception e) { - StringBuilder b = new StringBuilder(); - b.append("Failed to parse database resource["); - b.append(myFhirContext.getResourceType(resourceType)); - b.append("/"); - b.append(theEntity.getIdDt().getIdPart()); - b.append(" (pid "); - b.append(theEntity.getId()); - b.append(", version "); - b.append(theEntity.getFhirVersion().name()); - b.append("): "); - b.append(e.getMessage()); - String msg = b.toString(); - ourLog.error(msg, e); - throw new DataFormatException(Msg.code(928) + msg, e); - } - - } else { - - retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance(); - - } - - // 5. fill MetaData - retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, resourceType, retVal); - - // 6. Handle source (provenance) - if (isNotBlank(provenanceRequestId) || isNotBlank(provenanceSourceUri)) { - String sourceString = cleanProvenanceSourceUri(provenanceSourceUri) - + (isNotBlank(provenanceRequestId) ? "#" : "") - + defaultString(provenanceRequestId); - - MetaUtil.setSource(myContext, retVal, sourceString); - } - - // 7. Add partition information - if (myPartitionSettings.isPartitioningEnabled()) { - PartitionablePartitionId partitionId = theEntity.getPartitionId(); - if (partitionId != null && partitionId.getPartitionId() != null) { - PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId()); - retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId()); - } else { - retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null); - } - } - - return retVal; - } - - protected R populateResourceMetadata(IBaseResourceEntity theEntity, boolean theForHistoryOperation, @Nullable Collection tagList, long theVersion, Class theResourceType, R theResource) { - if (theResource instanceof IResource) { - IResource res = (IResource) theResource; - theResource = populateResourceMetadataHapi(theResourceType, theEntity, tagList, theForHistoryOperation, res, theVersion); - } else { - IAnyResource res = (IAnyResource) theResource; - theResource = populateResourceMetadataRi(theResourceType, theEntity, tagList, theForHistoryOperation, res, theVersion); - } - return theResource; - } - - public String toResourceName(Class theResourceType) { - return myContext.getResourceType(theResourceType); - } String toResourceName(IBaseResource theResource) { return myContext.getResourceType(theResource); @@ -1375,7 +1013,7 @@ public abstract class BaseHapiFhirDao extends BaseStora if (thePerformIndexing && changed != null && !changed.isChanged() && !theForceUpdate && myConfig.isSuppressUpdatesWithNoChange() && (entity.getVersion() > 1 || theUpdateVersion)) { ourLog.debug("Resource {} has not changed", entity.getIdDt().toUnqualified().getValue()); if (theResource != null) { - updateResourceMetadata(entity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(entity, theResource); } entity.setUnchangedInCurrentOperation(true); return entity; @@ -1475,7 +1113,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } if (theResource != null) { - updateResourceMetadata(entity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(entity, theResource); } @@ -1498,7 +1136,7 @@ public abstract class BaseHapiFhirDao extends BaseStora if (getConfig().isMassIngestionMode()) { oldResource = null; } else { - oldResource = toResource(entity, false); + oldResource = myJpaStorageResourceParser.toResource(entity, false); } notifyInterceptors(theRequest, theResource, oldResource, theTransactionDetails, true); @@ -1510,7 +1148,7 @@ public abstract class BaseHapiFhirDao extends BaseStora historyEntity = ((ResourceTable) readEntity(theResourceId, theRequest)).getCurrentVersionEntity(); // Update version/lastUpdated so that interceptors see the correct version - updateResourceMetadata(savedEntity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(savedEntity, theResource); // Populate the PID in the resource, so it is available to hooks addPidToResource(savedEntity, theResource); @@ -1537,7 +1175,7 @@ public abstract class BaseHapiFhirDao extends BaseStora if (!changed && myConfig.isSuppressUpdatesWithNoChange() && (historyEntity.getVersion() > 1)) { ourLog.debug("Resource {} has not changed", historyEntity.getIdDt().toUnqualified().getValue()); - updateResourceMetadata(historyEntity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(historyEntity, theResource); return historyEntity; } @@ -1556,7 +1194,7 @@ public abstract class BaseHapiFhirDao extends BaseStora historyEntity.setResourceTextVc(encodedResource.getResourceText()); myResourceHistoryTableDao.save(historyEntity); - updateResourceMetadata(historyEntity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(historyEntity, theResource); return historyEntity; } @@ -1586,14 +1224,15 @@ public abstract class BaseHapiFhirDao extends BaseStora /** * TODO eventually consider refactoring this to be part of an interceptor. - * + *

    * Throws an exception if the partition of the request, and the partition of the existing entity do not match. + * * @param theRequest the request. - * @param entity the existing entity. + * @param entity the existing entity. */ private void failIfPartitionMismatch(RequestDetails theRequest, ResourceTable entity) { if (myPartitionSettings.isPartitioningEnabled() && theRequest != null && theRequest.getTenantId() != null && entity.getPartitionId() != null && - theRequest.getTenantId() != ALL_PARTITIONS_NAME) { + !ALL_PARTITIONS_NAME.equals(theRequest.getTenantId())) { PartitionEntity partitionEntity = myPartitionLookupSvc.getPartitionByName(theRequest.getTenantId()); //partitionEntity should never be null if (partitionEntity != null && !partitionEntity.getId().equals(entity.getPartitionId().getPartitionId())) { @@ -1668,8 +1307,8 @@ public abstract class BaseHapiFhirDao extends BaseStora } @Override - public ResourceTable updateInternal(RequestDetails theRequestDetails, T theResource, boolean thePerformIndexing, boolean theForceUpdateVersion, - IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, TransactionDetails theTransactionDetails) { + public DaoMethodOutcome updateInternal(RequestDetails theRequestDetails, T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, + IBasePersistedResource theEntity, IIdType theResourceId, @Nullable IBaseResource theOldResource, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) { ResourceTable entity = (ResourceTable) theEntity; @@ -1696,7 +1335,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } // Update version/lastUpdated so that interceptors see the correct version - updateResourceMetadata(savedEntity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(savedEntity, theResource); // Populate the PID in the resource so it is available to hooks addPidToResource(savedEntity, theResource); @@ -1706,7 +1345,42 @@ public abstract class BaseHapiFhirDao extends BaseStora notifyInterceptors(theRequestDetails, theResource, theOldResource, theTransactionDetails, false); } - return savedEntity; + Collection tagList = Collections.emptyList(); + if (entity.isHasTags()) { + tagList = entity.getTags(); + } + long version = entity.getVersion(); + myJpaStorageResourceParser.populateResourceMetadata(entity, false, tagList, version, theResource); + + boolean wasDeleted = false; + // NB If this if-else ever gets collapsed, make sure to account for possible null (will happen in mass-ingestion mode) + if (theOldResource instanceof IResource) { + wasDeleted = ResourceMetadataKeyEnum.DELETED_AT.get((IResource) theOldResource) != null; + } else if (theOldResource instanceof IAnyResource) { + wasDeleted = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) theOldResource) != null; + } + + DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, theResource, theMatchUrl, theOperationType).setCreated(wasDeleted); + + if (!thePerformIndexing) { + IIdType id = getContext().getVersion().newIdType(); + id.setValue(entity.getIdDt().getValue()); + outcome.setId(id); + } + + // Only include a task timer if we're not in a sub-request (i.e. a transaction) + // since individual item times don't actually make much sense in the context + // of a transaction + StopWatch w = null; + if (theRequestDetails != null && !theRequestDetails.isSubRequest()) { + if (theTransactionDetails != null && !theTransactionDetails.isFhirTransaction()) { + w = new StopWatch(theTransactionDetails.getTransactionDate()); + } + } + + populateOperationOutcomeForUpdate(w, outcome, theMatchUrl, outcome.getOperationType()); + + return outcome; } private void notifyInterceptors(RequestDetails theRequestDetails, T theResource, IBaseResource theOldResource, TransactionDetails theTransactionDetails, boolean isUnchanged) { @@ -1735,26 +1409,7 @@ public abstract class BaseHapiFhirDao extends BaseStora } } - protected void updateResourceMetadata(IBaseResourceEntity theEntity, IBaseResource theResource) { - IIdType id = theEntity.getIdDt(); - if (getContext().getVersion().getVersion().isRi()) { - id = getContext().getVersion().newIdType().setValue(id.getValue()); - } - if (id.hasResourceType() == false) { - id = id.withResourceType(theEntity.getResourceType()); - } - - theResource.setId(id); - if (theResource instanceof IResource) { - ResourceMetadataKeyEnum.VERSION.put((IResource) theResource, id.getVersionIdPart()); - ResourceMetadataKeyEnum.UPDATED.put((IResource) theResource, theEntity.getUpdated()); - } else { - IBaseMetaType meta = theResource.getMeta(); - meta.setVersionId(id.getVersionIdPart()); - meta.setLastUpdated(theEntity.getUpdatedDate()); - } - } private void validateChildReferenceTargetTypes(IBase theElement, String thePath) { if (theElement == null) { @@ -1896,6 +1551,22 @@ public abstract class BaseHapiFhirDao extends BaseStora myPartitionSettings = thePartitionSettings; } + private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization { + + private final TagDefinition myTagDefinition; + private final MemoryCacheService.TagDefinitionCacheKey myKey; + + public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) { + myTagDefinition = theTagDefinition; + myKey = theKey; + } + + @Override + public void afterCommit() { + myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition); + } + } + @Nonnull public static MemoryCacheService.TagDefinitionCacheKey toTagDefinitionMemoryCacheKey(TagTypeEnum theTagType, String theScheme, String theTerm) { return new MemoryCacheService.TagDefinitionCacheKey(theTagType, theScheme, theTerm); @@ -1999,34 +1670,12 @@ public abstract class BaseHapiFhirDao extends BaseStora ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest; } - private static List toBaseCodingList(List theSecurityLabels) { - ArrayList retVal = new ArrayList<>(theSecurityLabels.size()); - for (IBaseCoding next : theSecurityLabels) { - retVal.add((BaseCodingDt) next); - } - return retVal; - } - - public static void validateResourceType(BaseHasResource theEntity, String theResourceName) { - if (!theResourceName.equals(theEntity.getResourceType())) { - throw new ResourceNotFoundException(Msg.code(935) + "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType()); - } - } - - private class AddTagDefinitionToCacheAfterCommitSynchronization implements TransactionSynchronization { - - private final TagDefinition myTagDefinition; - private final MemoryCacheService.TagDefinitionCacheKey myKey; - - public AddTagDefinitionToCacheAfterCommitSynchronization(MemoryCacheService.TagDefinitionCacheKey theKey, TagDefinition theTagDefinition) { - myTagDefinition = theTagDefinition; - myKey = theKey; - } - - @Override - public void afterCommit() { - myMemoryCacheService.put(MemoryCacheService.CacheEnum.TAG_DEFINITION, myKey, myTagDefinition); - } + /** + * Do not call this method outside of unit tests + */ + @VisibleForTesting + public void setJpaStorageResourceParserForUnitTest(IJpaStorageResourceParser theJpaStorageResourceParser) { + myJpaStorageResourceParser = theJpaStorageResourceParser; } } 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 afda3ead6f4..ae584a2d5a6 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 @@ -57,26 +57,23 @@ import ca.uhn.fhir.jpa.model.search.SearchRuntimeDetails; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.partition.IRequestPartitionHelperSvc; import ca.uhn.fhir.jpa.partition.SystemRequestDetails; -import ca.uhn.fhir.jpa.patch.FhirPatch; -import ca.uhn.fhir.jpa.patch.JsonPatchUtils; -import ca.uhn.fhir.jpa.patch.XmlPatchUtils; import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory; import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.ResourceSearch; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; import ca.uhn.fhir.model.dstu2.resource.ListResource; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.DataFormatException; -import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.SearchContainedModeEnum; import ca.uhn.fhir.rest.api.ValidationModeEnum; @@ -104,9 +101,9 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import ca.uhn.fhir.util.ObjectUtil; -import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.validation.FhirValidator; import ca.uhn.fhir.validation.IInstanceValidatorModule; import ca.uhn.fhir.validation.IValidationContext; @@ -118,7 +115,6 @@ import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseCoding; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; @@ -160,7 +156,6 @@ public abstract class BaseHapiFhirResourceDao extends B public static final String BASE_RESOURCE_NAME = "resource"; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class); - @Autowired protected PlatformTransactionManager myPlatformTransactionManager; @Autowired(required = false) @@ -181,18 +176,37 @@ public abstract class BaseHapiFhirResourceDao extends B private IDeleteExpungeJobSubmitter myDeleteExpungeJobSubmitter; @Autowired private IJobCoordinator myJobCoordinator; - private IInstanceValidatorModule myInstanceValidator; private String myResourceName; private Class myResourceType; - + @Autowired + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; @Autowired private MemoryCacheService myMemoryCacheService; private TransactionTemplate myTxTemplate; - @Autowired private UrlPartitioner myUrlPartitioner; + @Override + protected HapiTransactionService getTransactionService() { + return myTransactionService; + } + + @VisibleForTesting + public void setTransactionService(HapiTransactionService theTransactionService) { + myTransactionService = theTransactionService; + } + + @Override + protected MatchResourceUrlService getMatchResourceUrlService() { + return myMatchResourceUrlService; + } + + @Override + protected IStorageResourceParser getStorageResourceParser() { + return myJpaStorageResourceParser; + } + /** * @deprecated Use {@link #create(T, RequestDetails)} instead */ @@ -219,11 +233,6 @@ public abstract class BaseHapiFhirResourceDao extends B return create(theResource, theIfNoneExist, true, new TransactionDetails(), theRequestDetails); } - @VisibleForTesting - public void setTransactionService(HapiTransactionService theTransactionService) { - myTransactionService = theTransactionService; - } - @Override public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) { return myTransactionService.execute(theRequestDetails, theTransactionDetails, tx -> doCreateForPost(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails)); @@ -259,14 +268,14 @@ public abstract class BaseHapiFhirResourceDao extends B } RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName()); - return doCreateForPostOrPut(theResource, theIfNoneExist, thePerformIndexing, theTransactionDetails, theRequestDetails, requestPartitionId); + return doCreateForPostOrPut(theRequestDetails, theResource, theIfNoneExist, true, thePerformIndexing, requestPartitionId, RestOperationTypeEnum.CREATE, theTransactionDetails); } /** * Called both for FHIR create (POST) operations (via {@link #doCreateForPost(IBaseResource, String, boolean, TransactionDetails, RequestDetails)} * as well as for FHIR update (PUT) where we're doing a create-with-client-assigned-ID (via {@link #doUpdate(IBaseResource, String, boolean, boolean, RequestDetails, TransactionDetails)}. */ - private DaoMethodOutcome doCreateForPostOrPut(T theResource, String theIfNoneExist, boolean thePerformIndexing, TransactionDetails theTransactionDetails, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { + private DaoMethodOutcome doCreateForPostOrPut(RequestDetails theRequest, T theResource, String theMatchUrl, boolean theProcessMatchUrl, boolean thePerformIndexing, RequestPartitionId theRequestPartitionId, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) { StopWatch w = new StopWatch(); preProcessResourceForStorage(theResource); @@ -275,13 +284,13 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = new ResourceTable(); entity.setResourceType(toResourceName(theResource)); entity.setPartitionId(myRequestPartitionHelperService.toStoragePartition(theRequestPartitionId)); - entity.setCreatedByMatchUrl(theIfNoneExist); + entity.setCreatedByMatchUrl(theMatchUrl); entity.setVersion(1); - if (isNotBlank(theIfNoneExist)) { - Set match = myMatchResourceUrlService.processMatchUrl(theIfNoneExist, myResourceType, theTransactionDetails, theRequest); + if (isNotBlank(theMatchUrl) && theProcessMatchUrl) { + Set match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest); if (match.size() > 1) { - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theIfNoneExist, match.size()); + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theMatchUrl, match.size()); throw new PreconditionFailedException(Msg.code(958) + msg); } else if (match.size() == 1) { ResourcePersistentId pid = match.iterator().next(); @@ -289,7 +298,7 @@ public abstract class BaseHapiFhirResourceDao extends B Supplier entitySupplier = () -> { return myTxTemplate.execute(tx -> { ResourceTable foundEntity = myEntityManager.find(ResourceTable.class, pid.getId()); - IBaseResource resource = toResource(foundEntity, false); + IBaseResource resource = myJpaStorageResourceParser.toResource(foundEntity, false); theResource.setId(resource.getIdElement().getValue()); return new LazyDaoMethodOutcome.EntityAndResource(foundEntity, resource); }); @@ -314,7 +323,11 @@ public abstract class BaseHapiFhirResourceDao extends B }); }; - return toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true); + DaoMethodOutcome outcome = toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true); + StorageResponseCodeEnum responseCode = StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH; + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalWithMatch", w.getMillisAndRestart(), UrlUtil.sanitizeUrlPart(theMatchUrl)); + outcome.setOperationOutcome(createInfoOperationOutcome(msg, responseCode)); + return outcome; } } @@ -385,15 +398,15 @@ public abstract class BaseHapiFhirResourceDao extends B theTransactionDetails.addResolvedResourceId(persistentId.getAssociatedResourceId(), persistentId); // Pre-cache the match URL - if (theIfNoneExist != null) { - myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theIfNoneExist, persistentId); + if (theMatchUrl != null) { + myMatchResourceUrlService.matchUrlResolved(theTransactionDetails, getResourceName(), theMatchUrl, persistentId); } // Update the version/last updated in the resource so that interceptors get // the correct version // TODO - the above updateEntity calls updateResourceMetadata // Maybe we don't need this call here? - updateResourceMetadata(entity, theResource); + myJpaStorageResourceParser.updateResourceMetadata(entity, theResource); // Populate the PID in the resource so it is available to hooks addPidToResource(entity, theResource); @@ -409,15 +422,15 @@ public abstract class BaseHapiFhirResourceDao extends B doCallHooks(theTransactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, hookParams); } - DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource).setCreated(true); + DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, theResource, theMatchUrl, theOperationType) + .setCreated(true); + if (!thePerformIndexing) { outcome.setId(theResource.getIdElement()); } - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); - outcome.setOperationOutcome(createInfoOperationOutcome(msg)); + populateOperationOutcomeForUpdate(w, outcome, theMatchUrl, theOperationType); - ourLog.debug(msg); return outcome; } @@ -531,8 +544,7 @@ public abstract class BaseHapiFhirResourceDao extends B // if not found, return an outcome anyways. // Because no object actually existed, we'll // just set the id and nothing else - DaoMethodOutcome outcome = createMethodOutcomeForResourceId(theId.getValue(), MESSAGE_KEY_DELETE_RESOURCE_NOT_EXISTING); - return outcome; + return createMethodOutcomeForResourceId(theId.getValue(), MESSAGE_KEY_DELETE_RESOURCE_NOT_EXISTING, StorageResponseCodeEnum.SUCCESSFUL_DELETE_NOT_FOUND); } if (theId.hasVersionIdPart() && Long.parseLong(theId.getVersionIdPart()) != entity.getVersion()) { @@ -541,7 +553,7 @@ public abstract class BaseHapiFhirResourceDao extends B // Don't delete again if it's already deleted if (isDeleted(entity)) { - DaoMethodOutcome outcome = createMethodOutcomeForResourceId(entity.getIdDt().getValue(), MESSAGE_KEY_DELETE_RESOURCE_ALREADY_DELETED); + DaoMethodOutcome outcome = createMethodOutcomeForResourceId(entity.getIdDt().getValue(), MESSAGE_KEY_DELETE_RESOURCE_ALREADY_DELETED, StorageResponseCodeEnum.SUCCESSFUL_DELETE_ALREADY_DELETED); // used to exist, so we'll set the persistent id outcome.setPersistentId(new ResourcePersistentId(entity.getResourceId())); @@ -552,7 +564,7 @@ public abstract class BaseHapiFhirResourceDao extends B StopWatch w = new StopWatch(); - T resourceToDelete = toResource(myResourceType, entity, null, false); + T resourceToDelete = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false); theDeleteConflicts.setResourceIdMarkedForDeletion(theId); // Notify IServerOperationInterceptors about pre-action call @@ -581,14 +593,11 @@ public abstract class BaseHapiFhirResourceDao extends B doCallHooks(theTransactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams); - DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete).setCreated(true); + DaoMethodOutcome outcome = toMethodOutcome(theRequestDetails, savedEntity, resourceToDelete, null, RestOperationTypeEnum.DELETE).setCreated(true); - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - String message = getContext().getLocalizer().getMessage(BaseStorageDao.class, "successfulDeletes", 1, w.getMillis()); - String severity = "information"; - String code = "informational"; - OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); - outcome.setOperationOutcome(oo); + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulDeletes", 1); + msg += " " + getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulTimingSuffix", w.getMillis()); + outcome.setOperationOutcome(createInfoOperationOutcome(msg, StorageResponseCodeEnum.SUCCESSFUL_DELETE)); return outcome; } @@ -669,7 +678,7 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = myEntityManager.find(ResourceTable.class, pid.getIdAsLong()); deletedResources.add(entity); - T resourceToDelete = toResource(myResourceType, entity, null, false); + T resourceToDelete = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false); // Notify IServerOperationInterceptors about pre-action call HookParams hooks = new HookParams() @@ -703,17 +712,12 @@ public abstract class BaseHapiFhirResourceDao extends B IBaseOperationOutcome oo; if (deletedResources.isEmpty()) { - oo = OperationOutcomeUtil.newInstance(getContext()); - String message = getMessageSanitized("unableToDeleteNotFound", theUrl); - String severity = "warning"; - String code = "not-found"; - OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "unableToDeleteNotFound", theUrl); + oo = createOperationOutcome(OO_SEVERITY_WARN, msg, "not-found", StorageResponseCodeEnum.SUCCESSFUL_DELETE_NOT_FOUND); } else { - oo = OperationOutcomeUtil.newInstance(getContext()); - String message = getContext().getLocalizer().getMessage(BaseStorageDao.class, "successfulDeletes", deletedResources.size(), w.getMillis()); - String severity = "information"; - String code = "informational"; - OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulDeletes", deletedResources.size()); + msg += " " + getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulTimingSuffix", w.getMillis()); + oo = createInfoOperationOutcome(msg, StorageResponseCodeEnum.SUCCESSFUL_DELETE); } ourLog.debug("Processed delete on {} (matched {} resource(s)) in {}ms", theUrl, deletedResources.size(), w.getMillis()); @@ -745,7 +749,7 @@ public abstract class BaseHapiFhirResourceDao extends B } private void doMetaAdd(MT theMetaAdd, BaseHasResource theEntity, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) { - IBaseResource oldVersion = toResource(theEntity, false); + IBaseResource oldVersion = myJpaStorageResourceParser.toResource(theEntity, false); List tags = toTagList(theMetaAdd); for (TagDefinition nextDef : tags) { @@ -778,7 +782,7 @@ public abstract class BaseHapiFhirResourceDao extends B myEntityManager.merge(theEntity); // Interceptor call: STORAGE_PRECOMMIT_RESOURCE_UPDATED - IBaseResource newVersion = toResource(theEntity, false); + IBaseResource newVersion = myJpaStorageResourceParser.toResource(theEntity, false); HookParams preStorageParams = new HookParams() .add(IBaseResource.class, oldVersion) .add(IBaseResource.class, newVersion) @@ -802,7 +806,7 @@ public abstract class BaseHapiFhirResourceDao extends B private void doMetaDelete(MT theMetaDel, BaseHasResource theEntity, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) { // todo mb update hibernate search index if we are storing resources - it assumes inline tags. - IBaseResource oldVersion = toResource(theEntity, false); + IBaseResource oldVersion = myJpaStorageResourceParser.toResource(theEntity, false); List tags = toTagList(theMetaDel); @@ -824,7 +828,7 @@ public abstract class BaseHapiFhirResourceDao extends B theEntity = myEntityManager.merge(theEntity); // Interceptor call: STORAGE_PRECOMMIT_RESOURCE_UPDATED - IBaseResource newVersion = toResource(theEntity, false); + IBaseResource newVersion = myJpaStorageResourceParser.toResource(theEntity, false); HookParams preStorageParams = new HookParams() .add(IBaseResource.class, oldVersion) .add(IBaseResource.class, newVersion) @@ -889,6 +893,7 @@ public abstract class BaseHapiFhirResourceDao extends B } @Override + @Nonnull public String getResourceName() { return myResourceName; } @@ -908,7 +913,7 @@ public abstract class BaseHapiFhirResourceDao extends B @Transactional public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) { StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset); + IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, myResourceName, null, theSince, theUntil, theOffset); ourLog.debug("Processed history on {} in {}ms", myResourceName, w.getMillisAndRestart()); return retVal; } @@ -924,7 +929,7 @@ public abstract class BaseHapiFhirResourceDao extends B IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless(); BaseHasResource entity = readEntity(id, theRequest); - IBundleProvider retVal = super.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset); + IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(), theSince, theUntil, theOffset); ourLog.debug("Processed history on {} in {}ms", id, w.getMillisAndRestart()); return retVal; @@ -939,7 +944,7 @@ public abstract class BaseHapiFhirResourceDao extends B IIdType id = theId.withResourceType(myResourceName).toUnqualifiedVersionless(); BaseHasResource entity = readEntity(id, theRequest); - IBundleProvider retVal = super.history(theRequest, myResourceName, entity.getId(), + IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequest, myResourceName, entity.getId(), theHistorySearchDateRangeParam.getLowerBoundAsInstant(), theHistorySearchDateRangeParam.getUpperBoundAsInstant(), theHistorySearchDateRangeParam.getOffset(), @@ -1096,67 +1101,6 @@ public abstract class BaseHapiFhirResourceDao extends B return toMetaDt(theType, tagDefinitions); } - @Override - public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest) { - TransactionDetails transactionDetails = new TransactionDetails(); - return myTransactionService.execute(theRequest, transactionDetails, tx -> doPatch(theId, theConditionalUrl, thePatchType, thePatchBody, theFhirPatchBody, theRequest, transactionDetails)); - } - - private DaoMethodOutcome doPatch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequest, TransactionDetails theTransactionDetails) { - ResourceTable entityToUpdate; - if (isNotBlank(theConditionalUrl)) { - - Set match = myMatchResourceUrlService.processMatchUrl(theConditionalUrl, myResourceType, theTransactionDetails, theRequest); - if (match.size() > 1) { - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size()); - throw new PreconditionFailedException(Msg.code(972) + msg); - } else if (match.size() == 1) { - ResourcePersistentId pid = match.iterator().next(); - entityToUpdate = myEntityManager.find(ResourceTable.class, pid.getId()); - } else { - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "invalidMatchUrlNoMatches", theConditionalUrl); - throw new ResourceNotFoundException(Msg.code(973) + msg); - } - - } else { - entityToUpdate = readEntityLatestVersion(theId, theRequest, theTransactionDetails); - if (theId.hasVersionIdPart()) { - if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { - throw new ResourceVersionConflictException(Msg.code(974) + "Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); - } - } - } - - validateResourceType(entityToUpdate); - - if (isDeleted(entityToUpdate)) { - throw createResourceGoneException(entityToUpdate); - } - - IBaseResource resourceToUpdate = toResource(entityToUpdate, false); - IBaseResource destination; - switch (thePatchType) { - case JSON_PATCH: - destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); - break; - case XML_PATCH: - destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); - break; - case FHIR_PATCH_XML: - case FHIR_PATCH_JSON: - default: - IBaseParameters fhirPatchJson = theFhirPatchBody; - new FhirPatch(getContext()).apply(resourceToUpdate, fhirPatchJson); - destination = resourceToUpdate; - break; - } - - @SuppressWarnings("unchecked") - T destinationCasted = (T) destination; - myFhirContext.newJsonParser().setParserErrorHandler(new StrictErrorHandler()).encodeResourceToString(destinationCasted); - return update(destinationCasted, null, true, theRequest); - } - private boolean isDeleted(BaseHasResource entityToUpdate) { return entityToUpdate.getDeleted() != null; } @@ -1205,7 +1149,7 @@ public abstract class BaseHapiFhirResourceDao extends B throw createResourceGoneException(entity.get()); } - T retVal = toResource(myResourceType, entity.get(), null, false); + T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity.get(), null, false); ourLog.debug("Processed read on {} in {}ms", thePid, w.getMillis()); return retVal; @@ -1239,7 +1183,7 @@ public abstract class BaseHapiFhirResourceDao extends B BaseHasResource entity = readEntity(theId, theRequest); validateResourceType(entity); - T retVal = toResource(myResourceType, entity, null, false); + T retVal = myJpaStorageResourceParser.toResource(myResourceType, entity, null, false); if (theDeletedOk == false) { if (isDeleted(entity)) { @@ -1293,7 +1237,7 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = entityOpt.get(); try { - T resource = (T) toResource(entity, false); + T resource = (T) myJpaStorageResourceParser.toResource(entity, false); reindex(resource, entity); } catch (BaseServerResponseException | DataFormatException e) { myResourceTableDao.updateIndexStatus(entity.getId(), INDEX_STATUS_INDEXING_FAILED); @@ -1375,6 +1319,13 @@ public abstract class BaseHapiFhirResourceDao extends B return entity; } + @Override + protected IBasePersistedResource readEntityLatestVersion(ResourcePersistentId thePersistentId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) { + return myEntityManager.find(ResourceTable.class, thePersistentId.getIdAsLong()); + } + + + @Override @Nonnull protected ResourceTable readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) { RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequestForRead(theRequestDetails, getResourceName(), theId); @@ -1691,8 +1642,6 @@ public abstract class BaseHapiFhirResourceDao extends B } private DaoMethodOutcome doUpdate(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequest, TransactionDetails theTransactionDetails) { - StopWatch w = new StopWatch(); - T resource = theResource; preProcessResourceForStorage(resource); @@ -1701,6 +1650,7 @@ public abstract class BaseHapiFhirResourceDao extends B ResourceTable entity = null; IIdType resourceId; + RestOperationTypeEnum update = RestOperationTypeEnum.UPDATE; if (isNotBlank(theMatchUrl)) { Set match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest, theResource); if (match.size() > 1) { @@ -1711,7 +1661,8 @@ public abstract class BaseHapiFhirResourceDao extends B entity = myEntityManager.find(ResourceTable.class, pid.getId()); resourceId = entity.getIdDt(); } else { - DaoMethodOutcome outcome = create(resource, null, thePerformIndexing, theTransactionDetails, theRequest); + RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName()); + DaoMethodOutcome outcome = doCreateForPostOrPut(theRequest, resource, theMatchUrl, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails); // Pre-cache the match URL if (outcome.getPersistentId() != null) { @@ -1750,86 +1701,17 @@ public abstract class BaseHapiFhirResourceDao extends B } if (create) { - return doCreateForPostOrPut(resource, null, thePerformIndexing, theTransactionDetails, theRequest, requestPartitionId); + return doCreateForPostOrPut(theRequest, resource, null, false, thePerformIndexing, requestPartitionId, update, theTransactionDetails); } } - if (resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) != entity.getVersion()) { - throw new ResourceVersionConflictException(Msg.code(989) + "Trying to update " + resourceId + " but this is not the current version"); - } + // Start - if (resourceId.hasResourceType() && !resourceId.getResourceType().equals(getResourceName())) { - throw new UnprocessableEntityException(Msg.code(990) + "Invalid resource ID[" + entity.getIdDt().toUnqualifiedVersionless() + "] of type[" + entity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]"); - } - - IBaseResource oldResource; - if (getConfig().isMassIngestionMode()) { - oldResource = null; - } else { - oldResource = toResource(entity, false); - } - - /* - * Mark the entity as not deleted - This is also done in the actual updateInternal() - * method later on so it usually doesn't matter whether we do it here, but in the - * case of a transaction with multiple PUTs we don't get there until later so - * having this here means that a transaction can have a reference in one - * resource to another resource in the same transaction that is being - * un-deleted by the transaction. Wacky use case, sure. But it's real. - * - * See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources - * for a test that needs this. - */ - boolean wasDeleted = isDeleted(entity); - entity.setDeleted(null); - - /* - * If we aren't indexing, that means we're doing this inside a transaction. - * The transaction will do the actual storage to the database a bit later on, - * after placeholder IDs have been replaced, by calling {@link #updateInternal} - * directly. So we just bail now. - */ - if (!thePerformIndexing) { - resource.setId(entity.getIdDt().getValue()); - DaoMethodOutcome outcome = toMethodOutcome(theRequest, entity, resource).setCreated(wasDeleted); - outcome.setPreviousResource(oldResource); - if (!outcome.isNop()) { - // Technically this may not end up being right since we might not increment if the - // contents turn out to be the same - outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1))); - } - return outcome; - } - - /* - * Otherwise, we're not in a transaction - */ - ResourceTable savedEntity = updateInternal(theRequest, resource, thePerformIndexing, theForceUpdateVersion, entity, resourceId, oldResource, theTransactionDetails); - - if (thePerformIndexing) { - Collection tagList = Collections.emptyList(); - if (entity.isHasTags()) { - tagList = entity.getTags(); - } - long version = entity.getVersion(); - populateResourceMetadata(entity, false, tagList, version, getResourceType(), resource); - } - - DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, resource).setCreated(wasDeleted); - - if (!thePerformIndexing) { - IIdType id = getContext().getVersion().newIdType(); - id.setValue(entity.getIdDt().getValue()); - outcome.setId(id); - } - - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart()); - outcome.setOperationOutcome(createInfoOperationOutcome(msg)); - - ourLog.debug(msg); - return outcome; + return doUpdateForUpdateOrPatch(theRequest, resourceId, theMatchUrl, thePerformIndexing, theForceUpdateVersion, resource, entity, update, theTransactionDetails); } + + /** * Method for updating the historical version of the resource when a history version id is included in the request. * @@ -1844,8 +1726,8 @@ public abstract class BaseHapiFhirResourceDao extends B // No need for indexing as this will update a non-current version of the resource which will not be searchable preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, false); - BaseHasResource entity = null; - BaseHasResource currentEntity = null; + BaseHasResource entity; + BaseHasResource currentEntity; IIdType resourceId; @@ -1874,12 +1756,10 @@ public abstract class BaseHapiFhirResourceDao extends B entity.setDeleted(null); boolean isUpdatingCurrent = resourceId.hasVersionIdPart() && Long.parseLong(resourceId.getVersionIdPart()) == currentEntity.getVersion(); IBasePersistedResource savedEntity = updateHistoryEntity(theRequest, theResource, currentEntity, entity, resourceId, theTransactionDetails, isUpdatingCurrent); - DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource).setCreated(wasDeleted); + DaoMethodOutcome outcome = toMethodOutcome(theRequest, savedEntity, theResource, null, RestOperationTypeEnum.UPDATE).setCreated(wasDeleted); - String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdate", outcome.getId(), w.getMillisAndRestart()); - outcome.setOperationOutcome(createInfoOperationOutcome(msg)); + populateOperationOutcomeForUpdate(w, outcome, null, RestOperationTypeEnum.UPDATE); - ourLog.debug(msg); return outcome; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java index 78739652c06..20e882f7f0c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirSystemDao.java @@ -1,12 +1,20 @@ package ca.uhn.fhir.jpa.dao; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; +import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; +import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; +import ca.uhn.fhir.jpa.dao.expunge.ExpungeService; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.search.PersistedJpaBundleProviderFactory; import ca.uhn.fhir.jpa.search.builder.SearchBuilder; import ca.uhn.fhir.jpa.util.QueryChunker; import ca.uhn.fhir.jpa.util.ResourceCountCache; @@ -25,6 +33,9 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Nullable; import javax.annotation.PostConstruct; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; @@ -58,25 +69,36 @@ import java.util.stream.Collectors; * #L% */ -public abstract class BaseHapiFhirSystemDao extends BaseHapiFhirDao implements IFhirSystemDao { +public abstract class BaseHapiFhirSystemDao extends BaseStorageDao implements IFhirSystemDao { public static final Predicate[] EMPTY_PREDICATE_ARRAY = new Predicate[0]; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirSystemDao.class); public ResourceCountCache myResourceCountsCache; + + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; @Autowired private TransactionProcessor myTransactionProcessor; @Autowired private ApplicationContext myApplicationContext; + @Autowired + private ExpungeService myExpungeService; + @Autowired + private IResourceTableDao myResourceTableDao; + @Autowired + private PersistedJpaBundleProviderFactory myPersistedJpaBundleProviderFactory; + @Autowired + private IResourceTagDao myResourceTagDao; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; @VisibleForTesting public void setTransactionProcessorForUnitTest(TransactionProcessor theTransactionProcessor) { myTransactionProcessor = theTransactionProcessor; } - @Override @PostConstruct public void start() { - super.start(); myTransactionProcessor.setDao(this); } @@ -124,7 +146,7 @@ public abstract class BaseHapiFhirSystemDao extends B @Override public IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails) { StopWatch w = new StopWatch(); - IBundleProvider retVal = super.history(theRequestDetails, null, null, theSince, theUntil, theOffset); + IBundleProvider retVal = myPersistedJpaBundleProviderFactory.history(theRequestDetails, null, null, theSince, theUntil, theOffset); ourLog.info("Processed global history in {}ms", w.getMillisAndRestart()); return retVal; } @@ -259,4 +281,25 @@ public abstract class BaseHapiFhirSystemDao extends B return null; } + + @Override + protected IInterceptorBroadcaster getInterceptorBroadcaster() { + return myInterceptorBroadcaster; + } + + @Override + protected DaoConfig getConfig() { + return myDaoConfig; + } + + @Override + public FhirContext getContext() { + return myFhirContext; + } + + @VisibleForTesting + public void setDaoConfigForUnitTest(DaoConfig theDaoConfig) { + myDaoConfig = theDaoConfig; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java index d9568639f4c..430b458b5c7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirSystemDaoDstu2.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao; * #L% */ +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.jpa.model.entity.TagDefinition; import ca.uhn.fhir.model.dstu2.composite.MetaDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaStorageResourceParser.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaStorageResourceParser.java new file mode 100644 index 00000000000..dd0f8ab16b7 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IJpaStorageResourceParser.java @@ -0,0 +1,52 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2022 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% + */ + +import ca.uhn.fhir.jpa.model.entity.BaseTag; +import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity; +import ca.uhn.fhir.jpa.model.entity.ResourceTag; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import javax.annotation.Nullable; +import java.util.Collection; + +public interface IJpaStorageResourceParser extends IStorageResourceParser { + + /** + * Convert a storage entity into a FHIR resource model instance + */ + R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation); + + /** + * Populate the metadata (Resource.meta.*) from a storage entity and other related + * objects pulled from the database + */ + R populateResourceMetadata(IBaseResourceEntity theEntitySource, boolean theForHistoryOperation, @Nullable Collection tagList, long theVersion, R theResourceTarget); + + /** + * Populates a resource model object's metadata (Resource.meta.*) based on the + * values from a stroage entity. + * + * @param theEntitySource The source + * @param theResourceTarget The target + */ + void updateResourceMetadata(IBaseResourceEntity theEntitySource, IBaseResource theResourceTarget); +} 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 new file mode 100644 index 00000000000..9856d819579 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/JpaStorageResourceParser.java @@ -0,0 +1,490 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR JPA Server + * %% + * Copyright (C) 2014 - 2022 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% + */ + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.dao.IDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; +import ca.uhn.fhir.jpa.entity.PartitionEntity; +import ca.uhn.fhir.jpa.entity.ResourceSearchView; +import ca.uhn.fhir.jpa.model.config.PartitionSettings; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.model.entity.BaseTag; +import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity; +import ca.uhn.fhir.jpa.model.entity.PartitionablePartitionId; +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.jpa.model.entity.ResourceTag; +import ca.uhn.fhir.jpa.model.entity.TagTypeEnum; +import ca.uhn.fhir.jpa.partition.IPartitionLookupSvc; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.Tag; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.model.base.composite.BaseCodingDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.InstantDt; +import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; +import ca.uhn.fhir.parser.DataFormatException; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.parser.LenientErrorHandler; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.util.MetaUtil; +import org.apache.commons.lang3.Validate; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.r4.model.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.cleanProvenanceSourceUri; +import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.decodeResource; +import static org.apache.commons.lang3.StringUtils.defaultString; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public class JpaStorageResourceParser implements IJpaStorageResourceParser { + public static final LenientErrorHandler LENIENT_ERROR_HANDLER = new LenientErrorHandler(false).setErrorOnInvalidValue(false); + private static final Logger ourLog = LoggerFactory.getLogger(JpaStorageResourceParser.class); + @Autowired + private FhirContext myContext; + @Autowired + private DaoConfig myDaoConfig; + @Autowired + private IResourceHistoryTableDao myResourceHistoryTableDao; + @Autowired + private PartitionSettings myPartitionSettings; + @Autowired + private IPartitionLookupSvc myPartitionLookupSvc; + + @Override + public IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation) { + RuntimeResourceDefinition type = myContext.getResourceDefinition(theEntity.getResourceType()); + Class resourceType = type.getImplementingClass(); + return toResource(resourceType, (IBaseResourceEntity) theEntity, null, theForHistoryOperation); + } + + @Override + public R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation) { + + // 1. get resource, it's encoding and the tags if any + byte[] resourceBytes; + String resourceText; + ResourceEncodingEnum resourceEncoding; + @Nullable + Collection tagList = Collections.emptyList(); + long version; + String provenanceSourceUri = null; + String provenanceRequestId = null; + + if (theEntity instanceof ResourceHistoryTable) { + ResourceHistoryTable history = (ResourceHistoryTable) theEntity; + resourceBytes = history.getResource(); + resourceText = history.getResourceTextVc(); + resourceEncoding = history.getEncoding(); + switch (myDaoConfig.getTagStorageMode()) { + case VERSIONED: + default: + if (history.isHasTags()) { + tagList = history.getTags(); + } + break; + case NON_VERSIONED: + if (history.getResourceTable().isHasTags()) { + tagList = history.getResourceTable().getTags(); + } + break; + case INLINE: + tagList = null; + } + version = history.getVersion(); + if (history.getProvenance() != null) { + provenanceRequestId = history.getProvenance().getRequestId(); + provenanceSourceUri = history.getProvenance().getSourceUri(); + } + } else if (theEntity instanceof ResourceTable) { + ResourceTable resource = (ResourceTable) theEntity; + ResourceHistoryTable history; + if (resource.getCurrentVersionEntity() != null) { + history = resource.getCurrentVersionEntity(); + } else { + version = theEntity.getVersion(); + history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version); + ((ResourceTable) theEntity).setCurrentVersionEntity(history); + + while (history == null) { + if (version > 1L) { + version--; + history = myResourceHistoryTableDao.findForIdAndVersionAndFetchProvenance(theEntity.getId(), version); + } else { + return null; + } + } + } + + resourceBytes = history.getResource(); + resourceEncoding = history.getEncoding(); + resourceText = history.getResourceTextVc(); + switch (myDaoConfig.getTagStorageMode()) { + case VERSIONED: + case NON_VERSIONED: + if (resource.isHasTags()) { + tagList = resource.getTags(); + } + break; + case INLINE: + tagList = null; + break; + } + version = history.getVersion(); + if (history.getProvenance() != null) { + provenanceRequestId = history.getProvenance().getRequestId(); + provenanceSourceUri = history.getProvenance().getSourceUri(); + } + } else if (theEntity instanceof ResourceSearchView) { + // This is the search View + ResourceSearchView view = (ResourceSearchView) theEntity; + resourceBytes = view.getResource(); + resourceText = view.getResourceTextVc(); + resourceEncoding = view.getEncoding(); + version = view.getVersion(); + provenanceRequestId = view.getProvenanceRequestId(); + provenanceSourceUri = view.getProvenanceSourceUri(); + switch (myDaoConfig.getTagStorageMode()) { + case VERSIONED: + case NON_VERSIONED: + if (theTagList != null) { + tagList = theTagList; + } + break; + case INLINE: + tagList = null; + break; + } + } else { + // something wrong + return null; + } + + // 2. get The text + String decodedResourceText = decodedResourceText(resourceBytes, resourceText, resourceEncoding); + + // 3. Use the appropriate custom type if one is specified in the context + Class resourceType = determineTypeToParse(theResourceType, tagList); + + // 4. parse the text to FHIR + R retVal = parseResource(theEntity, resourceEncoding, decodedResourceText, resourceType); + + // 5. fill MetaData + retVal = populateResourceMetadata(theEntity, theForHistoryOperation, tagList, version, retVal); + + // 6. Handle source (provenance) + populateResourceSource(provenanceSourceUri, provenanceRequestId, retVal); + + // 7. Add partition information + populateResourcePartitionInformation(theEntity, retVal); + + return retVal; + } + + private void populateResourcePartitionInformation(IBaseResourceEntity theEntity, R retVal) { + if (myPartitionSettings.isPartitioningEnabled()) { + PartitionablePartitionId partitionId = theEntity.getPartitionId(); + if (partitionId != null && partitionId.getPartitionId() != null) { + PartitionEntity persistedPartition = myPartitionLookupSvc.getPartitionById(partitionId.getPartitionId()); + retVal.setUserData(Constants.RESOURCE_PARTITION_ID, persistedPartition.toRequestPartitionId()); + } else { + retVal.setUserData(Constants.RESOURCE_PARTITION_ID, null); + } + } + } + + private void populateResourceSource(String provenanceSourceUri, String provenanceRequestId, R retVal) { + if (isNotBlank(provenanceRequestId) || isNotBlank(provenanceSourceUri)) { + String sourceString = cleanProvenanceSourceUri(provenanceSourceUri) + + (isNotBlank(provenanceRequestId) ? "#" : "") + + defaultString(provenanceRequestId); + + MetaUtil.setSource(myContext, retVal, sourceString); + } + } + + @SuppressWarnings("unchecked") + private R parseResource(IBaseResourceEntity theEntity, ResourceEncodingEnum resourceEncoding, String decodedResourceText, Class resourceType) { + R retVal; + if (resourceEncoding != ResourceEncodingEnum.DEL) { + + IParser parser = new TolerantJsonParser(getContext(theEntity.getFhirVersion()), LENIENT_ERROR_HANDLER, theEntity.getId()); + + try { + retVal = parser.parseResource(resourceType, decodedResourceText); + } catch (Exception e) { + StringBuilder b = new StringBuilder(); + b.append("Failed to parse database resource["); + b.append(myContext.getResourceType(resourceType)); + b.append("/"); + b.append(theEntity.getIdDt().getIdPart()); + b.append(" (pid "); + b.append(theEntity.getId()); + b.append(", version "); + b.append(theEntity.getFhirVersion().name()); + b.append("): "); + b.append(e.getMessage()); + String msg = b.toString(); + ourLog.error(msg, e); + throw new DataFormatException(Msg.code(928) + msg, e); + } + + } else { + + retVal = (R) myContext.getResourceDefinition(theEntity.getResourceType()).newInstance(); + + } + return retVal; + } + + @SuppressWarnings("unchecked") + private Class determineTypeToParse(Class theResourceType, @Nullable Collection tagList) { + Class resourceType = theResourceType; + if (tagList != null) { + if (myContext.hasDefaultTypeForProfile()) { + for (BaseTag nextTag : tagList) { + if (nextTag.getTag().getTagType() == TagTypeEnum.PROFILE) { + String profile = nextTag.getTag().getCode(); + if (isNotBlank(profile)) { + Class newType = myContext.getDefaultTypeForProfile(profile); + if (newType != null && theResourceType.isAssignableFrom(newType)) { + ourLog.debug("Using custom type {} for profile: {}", newType.getName(), profile); + resourceType = (Class) newType; + break; + } + } + } + } + } + } + return resourceType; + } + + @SuppressWarnings("unchecked") + @Override + public R populateResourceMetadata(IBaseResourceEntity theEntitySource, boolean theForHistoryOperation, @Nullable Collection tagList, long theVersion, R theResourceTarget) { + if (theResourceTarget instanceof IResource) { + IResource res = (IResource) theResourceTarget; + theResourceTarget = (R) populateResourceMetadataHapi(theEntitySource, tagList, theForHistoryOperation, res, theVersion); + } else { + IAnyResource res = (IAnyResource) theResourceTarget; + theResourceTarget = populateResourceMetadataRi(theEntitySource, tagList, theForHistoryOperation, res, theVersion); + } + return theResourceTarget; + } + + @SuppressWarnings("unchecked") + private R populateResourceMetadataHapi(IBaseResourceEntity theEntity, @Nullable Collection theTagList, boolean theForHistoryOperation, R res, Long theVersion) { + R retVal = res; + if (theEntity.getDeleted() != null) { + res = (R) myContext.getResourceDefinition(res).newInstance(); + retVal = res; + ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); + if (theForHistoryOperation) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.DELETE); + } + } else if (theForHistoryOperation) { + /* + * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT. + */ + Date published = theEntity.getPublished().getValue(); + Date updated = theEntity.getUpdated().getValue(); + if (published.equals(updated)) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.POST); + } else { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, BundleEntryTransactionMethodEnum.PUT); + } + } + + res.setId(theEntity.getIdDt().withVersion(theVersion.toString())); + + ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion())); + ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished()); + ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated()); + IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); + + if (theTagList != null) { + if (theEntity.isHasTags()) { + TagList tagList = new TagList(); + List securityLabels = new ArrayList<>(); + List profiles = new ArrayList<>(); + for (BaseTag next : theTagList) { + switch (next.getTag().getTagType()) { + case PROFILE: + profiles.add(new IdDt(next.getTag().getCode())); + break; + case SECURITY_LABEL: + IBaseCoding secLabel = (IBaseCoding) myContext.getVersion().newCodingDt(); + secLabel.setSystem(next.getTag().getSystem()); + secLabel.setCode(next.getTag().getCode()); + secLabel.setDisplay(next.getTag().getDisplay()); + securityLabels.add(secLabel); + break; + case TAG: + tagList.add(new Tag(next.getTag().getSystem(), next.getTag().getCode(), next.getTag().getDisplay())); + break; + } + } + if (tagList.size() > 0) { + ResourceMetadataKeyEnum.TAG_LIST.put(res, tagList); + } + if (securityLabels.size() > 0) { + ResourceMetadataKeyEnum.SECURITY_LABELS.put(res, toBaseCodingList(securityLabels)); + } + if (profiles.size() > 0) { + ResourceMetadataKeyEnum.PROFILES.put(res, profiles); + } + } + } + + return retVal; + } + + @SuppressWarnings("unchecked") + private R populateResourceMetadataRi(IBaseResourceEntity theEntity, @Nullable Collection theTagList, boolean theForHistoryOperation, IAnyResource res, Long theVersion) { + R retVal = (R) res; + if (theEntity.getDeleted() != null) { + res = (IAnyResource) myContext.getResourceDefinition(res).newInstance(); + retVal = (R) res; + ResourceMetadataKeyEnum.DELETED_AT.put(res, new InstantDt(theEntity.getDeleted())); + if (theForHistoryOperation) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.DELETE.toCode()); + } + } else if (theForHistoryOperation) { + /* + * If the create and update times match, this was when the resource was created so we should mark it as a POST. Otherwise, it's a PUT. + */ + Date published = theEntity.getPublished().getValue(); + Date updated = theEntity.getUpdated().getValue(); + if (published.equals(updated)) { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.POST.toCode()); + } else { + ResourceMetadataKeyEnum.ENTRY_TRANSACTION_METHOD.put(res, Bundle.HTTPVerb.PUT.toCode()); + } + } + + res.getMeta().setLastUpdated(null); + res.getMeta().setVersionId(null); + + updateResourceMetadata(theEntity, res); + res.setId(res.getIdElement().withVersion(theVersion.toString())); + + res.getMeta().setLastUpdated(theEntity.getUpdatedDate()); + IDao.RESOURCE_PID.put(res, theEntity.getResourceId()); + + if (theTagList != null) { + res.getMeta().getTag().clear(); + res.getMeta().getProfile().clear(); + res.getMeta().getSecurity().clear(); + for (BaseTag next : theTagList) { + switch (next.getTag().getTagType()) { + case PROFILE: + res.getMeta().addProfile(next.getTag().getCode()); + break; + case SECURITY_LABEL: + IBaseCoding sec = res.getMeta().addSecurity(); + sec.setSystem(next.getTag().getSystem()); + sec.setCode(next.getTag().getCode()); + sec.setDisplay(next.getTag().getDisplay()); + break; + case TAG: + IBaseCoding tag = res.getMeta().addTag(); + tag.setSystem(next.getTag().getSystem()); + tag.setCode(next.getTag().getCode()); + tag.setDisplay(next.getTag().getDisplay()); + break; + } + } + } + + return retVal; + } + + @Override + public void updateResourceMetadata(IBaseResourceEntity theEntitySource, IBaseResource theResourceTarget) { + IIdType id = theEntitySource.getIdDt(); + if (myContext.getVersion().getVersion().isRi()) { + id = myContext.getVersion().newIdType().setValue(id.getValue()); + } + + if (id.hasResourceType() == false) { + id = id.withResourceType(theEntitySource.getResourceType()); + } + + theResourceTarget.setId(id); + if (theResourceTarget instanceof IResource) { + ResourceMetadataKeyEnum.VERSION.put((IResource) theResourceTarget, id.getVersionIdPart()); + ResourceMetadataKeyEnum.UPDATED.put((IResource) theResourceTarget, theEntitySource.getUpdated()); + } else { + IBaseMetaType meta = theResourceTarget.getMeta(); + meta.setVersionId(id.getVersionIdPart()); + meta.setLastUpdated(theEntitySource.getUpdatedDate()); + } + } + + private FhirContext getContext(FhirVersionEnum theVersion) { + Validate.notNull(theVersion, "theVersion must not be null"); + if (theVersion == myContext.getVersion().getVersion()) { + return myContext; + } + return FhirContext.forCached(theVersion); + } + + private static String decodedResourceText(byte[] resourceBytes, String resourceText, ResourceEncodingEnum resourceEncoding) { + String decodedResourceText; + if (resourceText != null) { + decodedResourceText = resourceText; + } else { + decodedResourceText = decodeResource(resourceBytes, resourceEncoding); + } + return decodedResourceText; + } + + private static List toBaseCodingList(List theSecurityLabels) { + ArrayList retVal = new ArrayList<>(theSecurityLabels.size()); + for (IBaseCoding next : theSecurityLabels) { + retVal.add((BaseCodingDt) next); + } + return retVal; + } + +} diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java index 627606f9da0..bdbeb29bcb7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/expunge/ResourceExpungeService.java @@ -25,9 +25,10 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; -import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.svc.IIdHelperService; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.data.IForcedIdDao; +import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTagDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedComboStringUniqueDao; @@ -41,19 +42,18 @@ import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamStringDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamTokenDao; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao; -import ca.uhn.fhir.jpa.dao.data.IResourceHistoryProvenanceDao; import ca.uhn.fhir.jpa.dao.data.IResourceTableDao; import ca.uhn.fhir.jpa.dao.data.IResourceTagDao; import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao; import ca.uhn.fhir.jpa.model.entity.ForcedId; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; -import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; +import ca.uhn.fhir.rest.server.util.CompositeInterceptorBroadcaster; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -123,11 +123,13 @@ public class ResourceExpungeService implements IResourceExpungeService { private DaoConfig myDaoConfig; @Autowired private MemoryCacheService myMemoryCacheService; + @Autowired + private IJpaStorageResourceParser myJpaStorageResourceParser; @Override @Transactional public List findHistoricalVersionsOfNonDeletedResources(String theResourceName, ResourcePersistentId theResourceId, int theRemainingCount) { - if(isEmptyQuery(theRemainingCount)){ + if (isEmptyQuery(theRemainingCount)) { return Collections.EMPTY_LIST; } @@ -154,7 +156,7 @@ public class ResourceExpungeService implements IResourceExpungeService { @Override @Transactional public List findHistoricalVersionsOfDeletedResources(String theResourceName, ResourcePersistentId theResourceId, int theRemainingCount) { - if(isEmptyQuery(theRemainingCount)){ + if (isEmptyQuery(theRemainingCount)) { return Collections.EMPTY_LIST; } @@ -192,7 +194,7 @@ public class ResourceExpungeService implements IResourceExpungeService { * be optimized, but expunge is hopefully not frequently called on busy servers * so it shouldn't be too big a deal. */ - TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){ + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { myMemoryCacheService.invalidateAllCaches(); @@ -220,8 +222,7 @@ public class ResourceExpungeService implements IResourceExpungeService { private void callHooks(RequestDetails theRequestDetails, AtomicInteger theRemainingCount, ResourceHistoryTable theVersion, IdDt theId) { final AtomicInteger counter = new AtomicInteger(); if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, myInterceptorBroadcaster, theRequestDetails)) { - IFhirResourceDao resourceDao = myDaoRegistry.getResourceDao(theId.getResourceType()); - IBaseResource resource = resourceDao.toResource(theVersion, false); + IBaseResource resource = myJpaStorageResourceParser.toResource(theVersion, false); HookParams params = new HookParams() .add(AtomicInteger.class, counter) .add(IIdType.class, theId) @@ -324,7 +325,7 @@ public class ResourceExpungeService implements IResourceExpungeService { private void expungeHistoricalVersionsOfId(RequestDetails theRequestDetails, Long myResourceId, AtomicInteger theRemainingCount) { Pageable page; - synchronized (theRemainingCount){ + synchronized (theRemainingCount) { if (expungeLimitReached(theRemainingCount)) { return; } @@ -348,7 +349,7 @@ public class ResourceExpungeService implements IResourceExpungeService { return new SliceImpl<>(Collections.singletonList(myVersion.getId())); } - private boolean isEmptyQuery(int theCount){ + private boolean isEmptyQuery(int theCount) { return theCount <= 0; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/batch2/DeleteExpungeSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/batch2/DeleteExpungeSvcImpl.java index 8338456f9af..2cd3586d538 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/batch2/DeleteExpungeSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/delete/batch2/DeleteExpungeSvcImpl.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -41,7 +42,7 @@ public class DeleteExpungeSvcImpl implements IDeleteExpungeSvc { private final DeleteExpungeSqlBuilder myDeleteExpungeSqlBuilder; private final IFulltextSearchSvc myFullTextSearchSvc; - public DeleteExpungeSvcImpl(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, IFulltextSearchSvc theFullTextSearchSvc) { + public DeleteExpungeSvcImpl(EntityManager theEntityManager, DeleteExpungeSqlBuilder theDeleteExpungeSqlBuilder, @Autowired(required = false) IFulltextSearchSvc theFullTextSearchSvc) { myEntityManager = theEntityManager; myDeleteExpungeSqlBuilder = theDeleteExpungeSqlBuilder; myFullTextSearchSvc = theFullTextSearchSvc; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index 075a9a6b64e..8cad69da494 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -31,7 +31,9 @@ import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.api.svc.ISearchCoordinatorSvc; import ca.uhn.fhir.jpa.dao.HistoryBuilder; import ca.uhn.fhir.jpa.dao.HistoryBuilderFactory; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.ISearchBuilder; +import ca.uhn.fhir.jpa.dao.JpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.SearchBuilderFactory; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.SearchTypeEnum; @@ -104,13 +106,15 @@ public class PersistedJpaBundleProvider implements IBundleProvider { private RequestPartitionHelperSvc myRequestPartitionHelperSvc; @Autowired private DaoConfig myDaoConfig; + @Autowired + private MemoryCacheService myMemoryCacheService; + @Autowired + private IJpaStorageResourceParser myJpaStorageResourceParser; /* * Non autowired fields (will be different for every instance * of this class, since it's a prototype */ - @Autowired - private MemoryCacheService myMemoryCacheService; private Search mySearchEntity; private String myUuid; private SearchCacheStatusEnum myCacheStatus; @@ -162,7 +166,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider { resource = next; IFhirResourceDao dao = myDaoRegistry.getResourceDao(next.getResourceType()); - retVal.add(dao.toResource(resource, true)); + retVal.add(myJpaStorageResourceParser.toResource(resource, true)); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java index 0e77506db02..5ca61fa53ac 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProviderFactory.java @@ -23,11 +23,20 @@ package ca.uhn.fhir.jpa.search; import ca.uhn.fhir.jpa.config.JpaConfig; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.entity.Search; +import ca.uhn.fhir.jpa.entity.SearchTypeEnum; +import ca.uhn.fhir.jpa.model.search.SearchStatusEnum; import ca.uhn.fhir.jpa.search.builder.tasks.SearchTask; +import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.param.HistorySearchStyleEnum; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import java.util.Date; +import java.util.UUID; + +import static org.apache.commons.lang3.StringUtils.defaultIfBlank; + public class PersistedJpaBundleProviderFactory { @Autowired @@ -46,4 +55,28 @@ public class PersistedJpaBundleProviderFactory { public PersistedJpaSearchFirstPageBundleProvider newInstanceFirstPage(RequestDetails theRequestDetails, Search theSearch, SearchTask theTask, ISearchBuilder theSearchBuilder) { return (PersistedJpaSearchFirstPageBundleProvider) myApplicationContext.getBean(JpaConfig.PERSISTED_JPA_SEARCH_FIRST_PAGE_BUNDLE_PROVIDER, theRequestDetails, theSearch, theTask, theSearchBuilder); } + + + public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset) { + return history(theRequest, theResourceType, theResourcePid, theRangeStartInclusive, theRangeEndInclusive, theOffset, null); + } + + public IBundleProvider history(RequestDetails theRequest, String theResourceType, Long theResourcePid, Date theRangeStartInclusive, Date theRangeEndInclusive, Integer theOffset, HistorySearchStyleEnum searchParameterType) { + String resourceName = defaultIfBlank(theResourceType, null); + + Search search = new Search(); + search.setOffset(theOffset); + search.setDeleted(false); + search.setCreated(new Date()); + search.setLastUpdated(theRangeStartInclusive, theRangeEndInclusive); + search.setUuid(UUID.randomUUID().toString()); + search.setResourceType(resourceName); + search.setResourceId(theResourcePid); + search.setSearchType(SearchTypeEnum.HISTORY); + search.setStatus(SearchStatusEnum.FINISHED); + search.setHistorySearchStyle(searchParameterType); + + return newInstance(theRequest, search); + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index e5a8492ee3b..83b7a04e1c8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -39,6 +39,7 @@ import ca.uhn.fhir.jpa.config.HapiFhirLocalContainerEntityManagerFactoryBean; import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; import ca.uhn.fhir.jpa.dao.BaseStorageDao; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.IResultIterator; import ca.uhn.fhir.jpa.dao.ISearchBuilder; import ca.uhn.fhir.jpa.dao.data.IResourceSearchViewDao; @@ -144,20 +145,34 @@ public class SearchBuilder implements ISearchBuilder { @Deprecated public static final int MAXIMUM_PAGE_SIZE = SearchConstants.MAX_PAGE_SIZE; public static final int MAXIMUM_PAGE_SIZE_FOR_TESTING = 50; + public static final String RESOURCE_ID_ALIAS = "resource_id"; + public static final String RESOURCE_VERSION_ALIAS = "resource_version"; private static final Logger ourLog = LoggerFactory.getLogger(SearchBuilder.class); private static final ResourcePersistentId NO_MORE = new ResourcePersistentId(-1L); private static final String MY_TARGET_RESOURCE_PID = "myTargetResourcePid"; private static final String MY_SOURCE_RESOURCE_PID = "mySourceResourcePid"; private static final String MY_TARGET_RESOURCE_TYPE = "myTargetResourceType"; - private static final String MY_SOURCE_RESOURCE_TYPE = "mySourceResourceType"; private static final String MY_TARGET_RESOURCE_VERSION = "myTargetResourceVersion"; - public static final String RESOURCE_ID_ALIAS = "resource_id"; - public static final String RESOURCE_VERSION_ALIAS = "resource_version"; public static boolean myUseMaxPageSize50ForTest = false; + protected final IInterceptorBroadcaster myInterceptorBroadcaster; + protected final IResourceTagDao myResourceTagDao; private final String myResourceName; private final Class myResourceType; - + private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory; + private final SqlObjectFactory mySqlBuilderFactory; + private final HibernatePropertiesProvider myDialectProvider; + private final ModelConfig myModelConfig; + private final ISearchParamRegistry mySearchParamRegistry; + private final PartitionSettings myPartitionSettings; + private final DaoRegistry myDaoRegistry; + private final IResourceSearchViewDao myResourceSearchViewDao; + private final FhirContext myContext; + private final IIdHelperService myIdHelperService; + private final DaoConfig myDaoConfig; + private final IDao myCallingDao; + @PersistenceContext(type = PersistenceContextType.TRANSACTION) + protected EntityManager myEntityManager; private List myAlsoIncludePids; private CriteriaBuilder myCriteriaBuilder; private SearchParameterMap myParams; @@ -167,30 +182,12 @@ public class SearchBuilder implements ISearchBuilder { private Set myPidSet; private boolean myHasNextIteratorQuery = false; private RequestPartitionId myRequestPartitionId; - - @PersistenceContext(type = PersistenceContextType.TRANSACTION) - protected EntityManager myEntityManager; @Autowired(required = false) private IFulltextSearchSvc myFulltextSearchSvc; @Autowired(required = false) private IElasticsearchSvc myIElasticsearchSvc; - - private final HapiFhirLocalContainerEntityManagerFactoryBean myEntityManagerFactory; - private final SqlObjectFactory mySqlBuilderFactory; - private final HibernatePropertiesProvider myDialectProvider; - private final ModelConfig myModelConfig; - private final ISearchParamRegistry mySearchParamRegistry; - private final PartitionSettings myPartitionSettings; - protected final IInterceptorBroadcaster myInterceptorBroadcaster; - protected final IResourceTagDao myResourceTagDao; - private final DaoRegistry myDaoRegistry; - private final IResourceSearchViewDao myResourceSearchViewDao; - private final FhirContext myContext; - private final IIdHelperService myIdHelperService; - - private final DaoConfig myDaoConfig; - - private final IDao myCallingDao; + @Autowired + private IJpaStorageResourceParser myJpaStorageResourceParser; /** * Constructor @@ -893,7 +890,7 @@ public class SearchBuilder implements ISearchBuilder { IBaseResource resource = null; if (next != null) { - resource = myCallingDao.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation); + resource = myJpaStorageResourceParser.toResource(resourceType, next, tagMap.get(next.getId()), theForHistoryOperation); } if (resource == null) { ourLog.warn("Unable to find resource {}/{}/_history/{} in database", next.getResourceType(), next.getIdDt().getIdPart(), next.getVersion()); 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 c0d8a24b752..e5d2ed42eb3 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 @@ -38,6 +38,9 @@ import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; import ca.uhn.fhir.jpa.config.util.ConnectionPoolInfoProvider; import ca.uhn.fhir.jpa.config.util.IConnectionPoolInfoProvider; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; +import ca.uhn.fhir.jpa.dao.IStorageResourceParser; +import ca.uhn.fhir.jpa.dao.JpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; @@ -264,6 +267,8 @@ public class TermReadSvcImpl implements ITermReadSvc { private CachingValidationSupport myCachingValidationSupport; @Autowired private VersionCanonicalizer myVersionCanonicalizer; + @Autowired + private IJpaStorageResourceParser myJpaStorageResourceParser; @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { @@ -2434,7 +2439,7 @@ public class TermReadSvcImpl implements ITermReadSvc { + ForcedId.IDX_FORCEDID_TYPE_FID + " removed?"); IFhirResourceDao csDao = myDaoRegistry.getResourceDao("CodeSystem"); - IBaseResource cs = csDao.toResource(resultList.get(0), false); + IBaseResource cs = myJpaStorageResourceParser.toResource(resultList.get(0), false); return Optional.of(cs); } @@ -2523,7 +2528,7 @@ public class TermReadSvcImpl implements ITermReadSvc { private org.hl7.fhir.r4.model.ValueSet getValueSetFromResourceTable(ResourceTable theResourceTable) { Class type = getFhirContext().getResourceDefinition("ValueSet").getImplementingClass(); - IBaseResource valueSet = myDaoRegistry.getResourceDao("ValueSet").toResource(type, theResourceTable, null, false); + IBaseResource valueSet = myJpaStorageResourceParser.toResource(type, theResourceTable, null, false); return myVersionCanonicalizer.valueSetToCanonical(valueSet); } diff --git a/hapi-fhir-jpaserver-cql/pom.xml b/hapi-fhir-jpaserver-cql/pom.xml index 1e743702eef..5f2fbdca982 100644 --- a/hapi-fhir-jpaserver-cql/pom.xml +++ b/hapi-fhir-jpaserver-cql/pom.xml @@ -7,7 +7,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.1-SNAPSHOT + 6.3.2-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 87ef97cb206..f010b9a9d85 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index f1f6bfd85f4..c8a1b5a549a 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 61b7e9e962c..c0bc3542616 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java index 845a78afb1b..435afb8c6c4 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/cross/IBasePersistedResource.java @@ -20,14 +20,15 @@ package ca.uhn.fhir.jpa.model.cross; * #L% */ -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import java.util.Date; - public interface IBasePersistedResource extends IResourceLookup { IIdType getIdDt(); + long getVersion(); + + boolean isDeleted(); + + void setNotDeleted(); } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java index d035b99b551..77a5ac5b578 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ModelConfig.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.model.entity; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.Validate; import org.hl7.fhir.dstu2.model.Subscription; @@ -79,6 +80,30 @@ public class ModelConfig { private boolean myDefaultSearchParamsCanBeOverridden = true; private Set mySupportedSubscriptionTypes = new HashSet<>(); private boolean myCrossPartitionSubscription = false; + + /** + * If set to true, attempt to map terminology for bulk export jobs using the + * logic in + * {@link ResponseTerminologyTranslationSvc}. Default is false. + * + * @since 6.3.0 + */ + public boolean isNormalizeTerminologyForBulkExportJobs() { + return myNormalizeTerminologyForBulkExportJobs; + } + + /** + * If set to true, attempt to map terminology for bulk export jobs using the + * logic in + * {@link ResponseTerminologyTranslationSvc}. Default is false. + * + * @since 6.3.0 + */ + public void setNormalizeTerminologyForBulkExportJobs(boolean theNormalizeTerminologyForBulkExportJobs) { + myNormalizeTerminologyForBulkExportJobs = theNormalizeTerminologyForBulkExportJobs; + } + + private boolean myNormalizeTerminologyForBulkExportJobs = false; private String myEmailFromAddress = "noreply@unknown.com"; private String myWebsocketContextPath = DEFAULT_WEBSOCKET_CONTEXT_PATH; /** 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 c8d815d26f2..9b6161704fd 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 @@ -212,6 +212,16 @@ public class ResourceHistoryTable extends BaseHasResource implements Serializabl return myResourceVersion; } + @Override + public boolean isDeleted() { + return getDeleted() != null; + } + + @Override + public void setNotDeleted() { + setDeleted(null); + } + public void setVersion(long theVersion) { myResourceVersion = theVersion; } diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java index c03b26edd0a..441688b1158 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/entity/ResourceTable.java @@ -548,6 +548,16 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas return myVersion; } + @Override + public boolean isDeleted() { + return getDeleted() != null; + } + + @Override + public void setNotDeleted() { + setDeleted(null); + } + public void setVersion(long theVersion) { myVersion = theVersion; } diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 803a246028b..89f8286a7ae 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index 9d201a1d717..41893251dcd 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.3.1-SNAPSHOT + 6.3.2-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 853e2b8dc34..51e074993f9 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.3.1-SNAPSHOT + 6.3.2-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 5977ad827b6..713e990cb54 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java index adbf0b66a8c..17bc0f606ff 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3UpdateTest.java @@ -48,14 +48,16 @@ public class FhirResourceDaoDstu3UpdateTest extends BaseJpaDstu3Test { CodeSystem codeSystem = new CodeSystem(); codeSystem.setUrl("http://foo"); - IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualifiedVersionless(); + IIdType id = myCodeSystemDao.create(codeSystem, mySrd).getId().toUnqualifiedVersionless(); - myCodeSystemDao.delete(id); + myCodeSystemDao.delete(id, mySrd); codeSystem = new CodeSystem(); codeSystem.setUrl("http://foo"); - myCodeSystemDao.update(codeSystem, "Patient?name=FAM").getId().toUnqualifiedVersionless(); + IIdType id2 = myCodeSystemDao.update(codeSystem, "CodeSystem?url=http://foo", mySrd).getId(); + assertNotEquals(id.getIdPart(), id2.getIdPart()); + assertEquals("1", id2.getVersionIdPart()); } @Test diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 41e0eb898fc..7b07c15b765 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -1117,7 +1117,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { String encoded = myFhirContext.newXmlParser().encodeResourceToString(response.getOperationOutcome()); ourLog.info(encoded); assertThat(encoded, containsString( - "")); + "")); } finally { IOUtils.closeQuietly(resp); } @@ -1214,7 +1214,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took ")); } finally { response.close(); } @@ -1241,7 +1241,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Deletion failed.")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Nothing has been deleted.")); } finally { response.close(); } @@ -1322,7 +1322,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { MethodOutcome resp = myClient.delete().resourceById(id).execute(); OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took")); } /** diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java index 566a57d9ef4..943f46035b7 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/SystemProviderTransactionSearchDstu3Test.java @@ -192,14 +192,11 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test { @Test - public void testPatchUsingJsonPatch_Transaction() throws Exception { - String methodName = "testPatchUsingJsonPatch_Transaction"; + public void testPatchUsingJsonPatch_Transaction() { IIdType pid1; { Patient patient = new Patient(); patient.setActive(true); - patient.addIdentifier().setSystem("urn:system").setValue("0"); - patient.addName().setFamily(methodName).addGiven("Joe"); pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); } @@ -224,6 +221,7 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test { .getRequest().setUrl(pid1.getValue()).setMethod(HTTPVerb.PUT); Bundle bundle = ourClient.transaction().withBundle(input).execute(); + ourLog.info("Response: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); //Validate over all bundle response entry contents. assertThat(bundle.getType(), is(equalTo(Bundle.BundleType.TRANSACTIONRESPONSE))); diff --git a/hapi-fhir-jpaserver-test-r4/pom.xml b/hapi-fhir-jpaserver-test-r4/pom.xml index 6dfc992e56a..41156640b1f 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java index 279c16c19aa..ad9c58d08c0 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDaoTest.java @@ -64,11 +64,11 @@ import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class BaseHapiFhirDaoTest { - private static class TestDao extends BaseHapiFhirDao { + private static class TestDao extends BaseHapiFhirResourceDao { @Nullable @Override - protected String getResourceName() { + public String getResourceName() { return "Patient"; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java index 7910be524ca..2b881a7a20f 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InterceptorTest.java @@ -171,7 +171,7 @@ public class FhirResourceDaoR4InterceptorTest extends BaseJpaR4Test { p = new Patient(); p.addName().setFamily("PATIENT3"); - id2 = myPatientDao.update(p, "Patient?family=ZZZ", mySrd).getId().getIdPartAsLong(); + id2 = myPatientDao.update(p, "Patient?family=PATIENT3", mySrd).getId().getIdPartAsLong(); assertNotEquals(id, id2); detailsCapt = ArgumentCaptor.forClass(RequestDetails.class); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TagsTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TagsTest.java index 01ab0be5d8a..a54994dc87d 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TagsTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4TagsTest.java @@ -4,8 +4,10 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.gclient.TokenClientParam; +import ca.uhn.fhir.util.BundleBuilder; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Enumerations; @@ -24,6 +26,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; @SuppressWarnings({"Duplicates"}) public class FhirResourceDaoR4TagsTest extends BaseResourceProviderR4Test { @@ -111,6 +115,119 @@ public class FhirResourceDaoR4TagsTest extends BaseResourceProviderR4Test { patient = (Patient) myPatientDao.update(patient, mySrd).getResource(); myCaptureQueriesListener.logAllQueries(); runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + assertThat(toProfiles(patient).toString(), toProfiles(patient), containsInAnyOrder("http://profile3")); + assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + + // Read it back + + patient = myPatientDao.read(new IdType("Patient/A"), mySrd); + assertThat(toProfiles(patient).toString(), toProfiles(patient), containsInAnyOrder("http://profile3")); + assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + + } + + /** + * Make sure tags are preserved + */ + @Test + public void testDeleteResourceWithTags_NonVersionedTags_InTransaction() { + initializeNonVersioned(); + when(mySrd.getHeader(eq(Constants.HEADER_PREFER))).thenReturn("return=representation"); + Bundle input, output; + + // Delete + + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + input = new BundleBuilder(myFhirContext) + .addTransactionDeleteEntry(new IdType("Patient/A")) + .andThen() + .getBundleTyped(); + output = mySystemDao.transaction(mySrd, input); + IIdType outcomeId = new IdType(output.getEntry().get(0).getResponse().getLocation()); + assertEquals("3", outcomeId.getVersionIdPart()); + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + + // Make sure $meta-get can fetch the tags of the deleted resource + + Meta meta = myPatientDao.metaGetOperation(Meta.class, new IdType("Patient/A"), mySrd); + assertThat(toProfiles(meta).toString(), toProfiles(meta), contains("http://profile2")); + assertThat(toTags(meta).toString(), toTags(meta), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + assertEquals("3", meta.getVersionId()); + + // Revive and verify + + Patient patient = new Patient(); + patient.setId("A"); + patient.getMeta().addProfile("http://profile3"); + patient.setActive(true); + + myCaptureQueriesListener.clear(); + + input = new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(patient) + .andThen() + .getBundleTyped(); + output = mySystemDao.transaction(mySrd, input); + patient = (Patient) output.getEntry().get(0).getResource(); + assert patient != null; + + assertThat(toProfiles(patient).toString(), toProfiles(patient), containsInAnyOrder("http://profile3")); + assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + myCaptureQueriesListener.logAllQueries(); + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + + // Read it back + + patient = myPatientDao.read(new IdType("Patient/A"), mySrd); + assertThat(toProfiles(patient).toString(), toProfiles(patient), containsInAnyOrder("http://profile3")); + assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + + } + + /** + * Make sure tags are preserved + */ + @Test + public void testDeleteResourceWithTags_VersionedTags_InTransaction() { + initializeVersioned(); + when(mySrd.getHeader(eq(Constants.HEADER_PREFER))).thenReturn("return=representation"); + Bundle input, output; + + // Delete + + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + input = new BundleBuilder(myFhirContext) + .addTransactionDeleteEntry(new IdType("Patient/A")) + .andThen() + .getBundleTyped(); + output = mySystemDao.transaction(mySrd, input); + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + + // Make sure $meta-get can fetch the tags of the deleted resource + + Meta meta = myPatientDao.metaGetOperation(Meta.class, new IdType("Patient/A"), mySrd); + assertThat(toProfiles(meta).toString(), toProfiles(meta), contains("http://profile2")); + assertThat(toTags(meta).toString(), toTags(meta), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); + + // Revive and verify + + Patient patient = new Patient(); + patient.setId("A"); + patient.getMeta().addProfile("http://profile3"); + patient.setActive(true); + + myCaptureQueriesListener.clear(); + input = new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(patient) + .andThen() + .getBundleTyped(); + output = mySystemDao.transaction(mySrd, input); + patient = (Patient) output.getEntry().get(0).getResource(); + assert patient != null; + myCaptureQueriesListener.logAllQueries(); + runInTransaction(() -> assertEquals(3, myResourceTagDao.count())); + assertThat(toProfiles(patient).toString(), toProfiles(patient), containsInAnyOrder("http://profile3")); + assertThat(toTags(patient).toString(), toTags(patient), containsInAnyOrder("http://tag1|vtag1|dtag1", "http://tag2|vtag2|dtag2")); // Read it back diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index f0acc71253c..849c909ff6f 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -469,22 +469,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { } @Test - public void testReCreateMatchResource() { - - CodeSystem codeSystem = new CodeSystem(); - codeSystem.setUrl("http://foo"); - IIdType id = myCodeSystemDao.create(codeSystem).getId().toUnqualifiedVersionless(); - - myCodeSystemDao.delete(id); - - codeSystem = new CodeSystem(); - codeSystem.setUrl("http://foo"); - myCodeSystemDao.update(codeSystem, "Patient?name=FAM").getId().toUnqualifiedVersionless(); - - } - - @Test - public void testUpdateAndGetHistoryResource() throws InterruptedException { + public void testUpdateAndGetHistoryResource() { Patient patient = new Patient(); patient.addIdentifier().setSystem("urn:system").setValue("001"); patient.addName().setFamily("Tester").addGiven("Joe"); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ResponseTerminologyTranslationInterceptorTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ResponseTerminologyTranslationInterceptorTest.java index a710a0f0a58..f7a28ade850 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ResponseTerminologyTranslationInterceptorTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/interceptor/ResponseTerminologyTranslationInterceptorTest.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.interceptor; import ca.uhn.fhir.jpa.api.model.BulkExportJobResults; import ca.uhn.fhir.jpa.api.svc.IBatch2JobRunner; import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.util.BulkExportUtils; import ca.uhn.fhir.rest.api.Constants; @@ -36,9 +37,8 @@ import static org.junit.jupiter.api.Assertions.fail; public class ResponseTerminologyTranslationInterceptorTest extends BaseResourceProviderR4Test { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseTerminologyTranslationInterceptorTest.class); public static final String TEST_OBV_FILTER = "Observation?status=amended"; - + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResponseTerminologyTranslationInterceptorTest.class); @Autowired private ResponseTerminologyTranslationInterceptor myResponseTerminologyTranslationInterceptor; @@ -55,6 +55,7 @@ public class ResponseTerminologyTranslationInterceptorTest extends BaseResourceP public void afterEach() { myResponseTerminologyTranslationInterceptor.clearMappingSpecifications(); myServer.unregisterInterceptor(myResponseTerminologyTranslationInterceptor); + myModelConfig.setNormalizeTerminologyForBulkExportJobs(new ModelConfig().isNormalizeTerminologyForBulkExportJobs()); } @Test @@ -139,6 +140,8 @@ public class ResponseTerminologyTranslationInterceptorTest extends BaseResourceP @Test public void testBulkExport_TerminologyTranslation_MappingFound() { + myModelConfig.setNormalizeTerminologyForBulkExportJobs(true); + // Create some resources to load Observation observation = new Observation(); observation.setStatus(Observation.ObservationStatus.AMENDED); @@ -157,6 +160,8 @@ public class ResponseTerminologyTranslationInterceptorTest extends BaseResourceP @Test public void testBulkExport_TerminologyTranslation_MappingNotNeeded() { + myModelConfig.setNormalizeTerminologyForBulkExportJobs(true); + // Create some resources to load Observation observation = new Observation(); observation.setStatus(Observation.ObservationStatus.AMENDED); @@ -176,6 +181,8 @@ public class ResponseTerminologyTranslationInterceptorTest extends BaseResourceP @Test public void testBulkExport_TerminologyTranslation_NoMapping() { + myModelConfig.setNormalizeTerminologyForBulkExportJobs(true); + // Create some resources to load Observation observation = new Observation(); observation.setStatus(Observation.ObservationStatus.AMENDED); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderMeaningfulOutcomeMessageR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderMeaningfulOutcomeMessageR4Test.java new file mode 100644 index 00000000000..c740cb191ee --- /dev/null +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderMeaningfulOutcomeMessageR4Test.java @@ -0,0 +1,686 @@ +package ca.uhn.fhir.jpa.provider.r4; + +import ca.uhn.fhir.i18n.HapiLocalizer; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.BaseStorageDao; +import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.util.BundleBuilder; +import org.hamcrest.Matcher; +import org.hl7.fhir.r4.model.BooleanType; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static ca.uhn.fhir.util.TestUtil.sleepAtLeast; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsInRelativeOrder; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.matchesPattern; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +@SuppressWarnings("Duplicates") +public class ResourceProviderMeaningfulOutcomeMessageR4Test extends BaseResourceProviderR4Test { + + @BeforeEach + @Override + public void before() throws Exception { + super.before(); + HapiLocalizer.setOurFailOnMissingMessage(true); + myDaoConfig.setAllowMultipleDelete(true); + } + + @AfterEach + @Override + public void after() { + myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete()); + } + + @Test + public void testCreateUpdateDelete() { + + // Initial Create-with-client-assigned-ID + + Patient p = new Patient(); + p.setId("Patient/A"); + p.setActive(true); + OperationOutcome oo = (OperationOutcome) myClient + .update() + .resource(p) + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateAsCreate", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_AS_CREATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Update with change + + p.setId("Patient/A"); + p.setActive(false); + oo = (OperationOutcome) myClient + .update() + .resource(p) + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdate", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Update with no change + + p.setId("Patient/A"); + oo = (OperationOutcome) myClient + .update() + .resource(p) + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Initial create: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateNoChange", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Delete + + oo = (OperationOutcome) myClient + .delete() + .resourceById("Patient", "A") + .execute() + .getOperationOutcome(); + ourLog.info("Delete: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulDeletes", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Delete with no change + + oo = (OperationOutcome) myClient + .delete() + .resourceById("Patient", "A") + .execute() + .getOperationOutcome(); + ourLog.info("Delete: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("deleteResourceAlreadyDeleted")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE_ALREADY_DELETED.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testCreateUpdateDelete_InTransaction() { + + // Initial Create-with-client-assigned-ID + + Patient p = new Patient(); + p.setId("Patient/A"); + p.setActive(true); + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Initial create: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateAsCreate", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_AS_CREATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Update with change + + p.setId("Patient/A"); + p.setActive(false); + input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .andThen() + .getBundle(); + output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdate")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Update with no change + + p.setId("Patient/A"); + input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .andThen() + .getBundle(); + output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateNoChange")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Delete + + input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionDeleteEntry("Patient", "A") + .andThen() + .getBundle(); + output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulDeletes", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + // Delete With No Change + + input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionDeleteEntry("Patient", "A") + .andThen() + .getBundle(); + output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("deleteResourceAlreadyDeleted")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE_ALREADY_DELETED.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testCreate_InTransaction() { + + Patient p = new Patient(); + p.setActive(true); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionCreateEntry(p) + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulCreate", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CREATE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalCreate_NoMatch_InTransaction() { + + Patient p = new Patient(); + p.setActive(true); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionCreateEntry(p) + .conditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), + matchesPattern("Successfully conditionally created resource \".*\". No existing resources matched URL \"Patient\\?active=true\". Took [0-9]+ms.")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CREATE_NO_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalCreate_WithMatch_InTransaction() { + createPatient(withActiveTrue()); + + Patient p = new Patient(); + p.setActive(true); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionCreateEntry(p) + .conditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulCreateConditionalWithMatch")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_NoMatch() { + Patient p = new Patient(); + p.setActive(true); + + OperationOutcome oo = (OperationOutcome) myClient + .update() + .resource(p) + .conditionalByUrl("Patient?active=true") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalNoMatch", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_WithMatchAndChange() { + createPatient(withActiveTrue()); + + Patient p = new Patient(); + p.setActive(true); + p.addName().setFamily("Test"); + + OperationOutcome oo = (OperationOutcome) myClient + .update() + .resource(p) + .conditionalByUrl("Patient?active=true") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalWithMatch", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_WithMatchNoChange() { + createPatient(withActiveTrue()); + + Patient p = new Patient(); + p.setActive(true); + + OperationOutcome oo = (OperationOutcome) myClient + .update() + .resource(p) + .conditionalByUrl("Patient?active=true") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalNoChangeWithMatch", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_NoMatch_InTransaction() { + Patient p = new Patient(); + p.setActive(true); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .conditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalNoMatch", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_WithMatchAndChange_InTransaction() { + createPatient(withActiveTrue()); + + Patient p = new Patient(); + p.setActive(true); + p.addName().setFamily("Test"); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .conditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalWithMatch")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testConditionalUpdate_WithMatchNoChange_InTransaction() { + createPatient(withActiveTrue()); + + Patient p = new Patient(); + p.setActive(true); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionUpdateEntry(p) + .conditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Create {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulUpdateConditionalNoChangeWithMatch")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testPatch_WithChanges() { + createPatient(withId("A"), withActiveTrue()); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + OperationOutcome oo = (OperationOutcome) myClient + .patch() + .withFhirPatch(patch) + .withId("Patient/A") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatch", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_PATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testPatch_NoChanges() { + createPatient(withId("A"), withActiveFalse()); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + OperationOutcome oo = (OperationOutcome) myClient + .patch() + .withFhirPatch(patch) + .withId("Patient/A") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchNoChange", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_PATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + + @Test + public void testPatch_Conditional_MatchWithChanges() { + createPatient(withId("A"), withActiveTrue(), withBirthdate("2022-01-01")); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + OperationOutcome oo = (OperationOutcome) myClient + .patch() + .withFhirPatch(patch) + .conditionalByUrl("Patient?birthdate=2022-01-01") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchConditional", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testPatch_Conditional_MatchNoChanges() { + createPatient(withId("A"), withActiveFalse(), withBirthdate("2022-01-01")); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + OperationOutcome oo = (OperationOutcome) myClient + .patch() + .withFhirPatch(patch) + .conditionalByUrl("Patient?birthdate=2022-01-01") + .prefer(PreferReturnEnum.OPERATION_OUTCOME) + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchConditionalNoChange", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + + @Test + public void testPatch_WithChanges_InTransaction() { + createPatient(withId("A"), withActiveTrue()); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + BundleBuilder bb = new BundleBuilder(myFhirContext); + bb.addTransactionFhirPatchEntry(new IdType("Patient/A"), patch); + + Bundle response = myClient + .transaction() + .withBundle((Bundle)bb.getBundle()) + .execute(); + OperationOutcome oo = (OperationOutcome) response.getEntry().get(0).getResponse().getOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatch")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_PATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testPatch_NoChanges_InTransaction() { + createPatient(withId("A"), withActiveFalse()); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + BundleBuilder bb = new BundleBuilder(myFhirContext); + bb.addTransactionFhirPatchEntry(new IdType("Patient/A"), patch); + + Bundle response = myClient + .transaction() + .withBundle((Bundle)bb.getBundle()) + .execute(); + OperationOutcome oo = (OperationOutcome) response.getEntry().get(0).getResponse().getOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchNoChange")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_PATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + + @Test + public void testPatch_Conditional_MatchWithChanges_InTransaction() { + createPatient(withId("A"), withActiveTrue(), withBirthdate("2022-01-01")); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + BundleBuilder bb = new BundleBuilder(myFhirContext); + bb.addTransactionFhirPatchEntry(patch).conditional("Patient?birthdate=2022-01-01"); + + Bundle response = myClient + .transaction() + .withBundle((Bundle)bb.getBundle()) + .execute(); + OperationOutcome oo = (OperationOutcome) response.getEntry().get(0).getResponse().getOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchConditional")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testPatch_Conditional_MatchNoChanges_InTransaction() { + createPatient(withId("A"), withActiveFalse(), withBirthdate("2022-01-01")); + + Parameters patch = createPatchToSetPatientActiveFalse(); + + BundleBuilder bb = new BundleBuilder(myFhirContext); + bb.addTransactionFhirPatchEntry(patch).conditional("Patient?birthdate=2022-01-01"); + + Bundle response = myClient + .transaction() + .withBundle((Bundle)bb.getBundle()) + .execute(); + OperationOutcome oo = (OperationOutcome) response.getEntry().get(0).getResponse().getOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulPatchConditionalNoChange")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + @Test + public void testMultiDelete_NoneFound() { + + OperationOutcome oo = (OperationOutcome) myClient + .delete() + .resourceConditionalByUrl("Patient?active=true") + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("unableToDeleteNotFound")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE_NOT_FOUND.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testMultiDelete_SomeFound() { + + createPatient(withActiveTrue()); + createPatient(withActiveTrue()); + createPatient(withActiveTrue()); + + OperationOutcome oo = (OperationOutcome) myClient + .delete() + .resourceConditionalByUrl("Patient?active=true") + .execute() + .getOperationOutcome(); + ourLog.info("Update: {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulDeletes", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + @Test + public void testMultiDelete_SomeFound_InTransaction() { + createPatient(withActiveTrue()); + createPatient(withActiveTrue()); + createPatient(withActiveTrue()); + + Bundle input = (Bundle) new BundleBuilder(myFhirContext) + .addTransactionDeleteEntryConditional("Patient?active=true") + .andThen() + .getBundle(); + Bundle output = myClient + .transaction() + .withBundle(input) + .execute(); + ourLog.info("Delete {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(output)); + OperationOutcome oo = (OperationOutcome) output.getEntry().get(0).getResponse().getOutcome(); + assertThat(oo.getIssueFirstRep().getDiagnostics(), matchesHapiMessage("successfulDeletes", "successfulTimingSuffix")); + assertEquals(StorageResponseCodeEnum.SUCCESSFUL_DELETE.name(), oo.getIssueFirstRep().getDetails().getCodingFirstRep().getCode()); + assertEquals(StorageResponseCodeEnum.SYSTEM, oo.getIssueFirstRep().getDetails().getCodingFirstRep().getSystem()); + + } + + private static Parameters createPatchToSetPatientActiveFalse() { + Parameters patch = new Parameters(); + Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation"); + op.addPart().setName("type").setValue(new CodeType("replace")); + op.addPart().setName("path").setValue(new CodeType("Patient.active")); + op.addPart().setName("value").setValue(new BooleanType(false)); + return patch; + } + + + private Matcher matchesHapiMessage(String... theMessageKey) { + StringBuilder joinedPattern = new StringBuilder(); + + for (var next : theMessageKey) { + String qualifiedKey = BaseStorageDao.class.getName() + "." + next; + String pattern = myFhirContext.getLocalizer().getFormatString(qualifiedKey); + assertTrue(isNotBlank(pattern)); + pattern = pattern + .replace("\"", "\\\"") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("[", "\\[") + .replace("]", "\\]") + .replace(".", "\\.") + .replaceAll("\\{[0-9]+}", ".*"); + + if (joinedPattern.length() > 0) { + joinedPattern.append(' '); + } + joinedPattern.append(pattern); + + } + + return matchesPattern(joinedPattern.toString()); + } + +} diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 98355bd7caa..97b7a4f0b30 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -1,7 +1,9 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.i18n.HapiLocalizer; import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.dao.BaseStorageDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -14,6 +16,7 @@ import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; import ca.uhn.fhir.jpa.term.ZipCollectionBuilder; import ca.uhn.fhir.jpa.test.config.TestR4Config; import ca.uhn.fhir.jpa.util.QueryParameterUtils; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; @@ -46,6 +49,7 @@ import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; +import ca.uhn.fhir.util.BundleBuilder; import ca.uhn.fhir.util.ClasspathUtil; import ca.uhn.fhir.util.StopWatch; import ca.uhn.fhir.util.UrlUtil; @@ -69,6 +73,7 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; +import org.hamcrest.Matcher; import org.hamcrest.Matchers; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.instance.model.api.IAnyResource; @@ -165,7 +170,6 @@ import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; import javax.annotation.Nonnull; -import javax.sql.DataSource; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -263,6 +267,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { public void before() throws Exception { super.before(); myFhirContext.setParserErrorHandler(new StrictErrorHandler()); + HapiLocalizer.setOurFailOnMissingMessage(true); myDaoConfig.setAllowMultipleDelete(true); myClient.registerInterceptor(myCapturingInterceptor); @@ -292,7 +297,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } @Test - public void createResourceSearchParameter_withExpressionMetaSecurity_succeeds(){ + public void createResourceSearchParameter_withExpressionMetaSecurity_succeeds() { SearchParameter searchParameter = new SearchParameter(); searchParameter.setId("resource-security"); searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); @@ -310,7 +315,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } @Test - public void createSearchParameter_with2Expressions_succeeds(){ + public void createSearchParameter_with2Expressions_succeeds() { SearchParameter searchParameter = new SearchParameter(); @@ -320,7 +325,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { searchParameter.setType(Enumerations.SearchParamType.TOKEN); searchParameter.setExpression("Patient.gender|Person.gender"); - MethodOutcome result= myClient.create().resource(searchParameter).execute(); + MethodOutcome result = myClient.create().resource(searchParameter).execute(); assertEquals(true, result.getCreated()); @@ -757,7 +762,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - @Test public void testCreateWithNoBody() throws IOException { @@ -817,7 +821,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - @BeforeEach public void beforeDisableResultReuse() { myDaoConfig.setReuseCachedSearchResultsForMillis(null); @@ -831,7 +834,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(200, resp.getStatusLine().getStatusCode()); } - private ArrayList genResourcesOfType(Bundle theRes, Class theClass) { ArrayList retVal = new ArrayList<>(); for (BundleEntryComponent next : theRes.getEntry()) { @@ -974,7 +976,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - @Test public void testCreateAndReadBackResourceWithContainedReferenceToContainer() { myFhirContext.setParserErrorHandler(new StrictErrorHandler()); @@ -1039,7 +1040,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals("#", loc.getManagingOrganization().getReference()); } - @Test public void testCountParam() { List resources = new ArrayList<>(); @@ -1099,7 +1099,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertNull(p.getBirthDate()); } - /** * See #438 */ @@ -1648,7 +1647,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String encoded = myFhirContext.newXmlParser().encodeResourceToString(response.getOperationOutcome()); ourLog.info(encoded); assertThat(encoded, containsString( - "")); + "" + )); } } @@ -1711,7 +1711,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } - @Test @Disabled public void testQuery() throws IOException { @@ -1752,7 +1751,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s). Took")); } finally { response.close(); } @@ -1779,7 +1778,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); ourLog.info(resp); OperationOutcome oo = myFhirContext.newXmlParser().parseResource(OperationOutcome.class, resp); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL \"Patient?name=testDeleteResourceConditional1\". Deletion failed.")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Unable to find resource matching URL")); } finally { response.close(); } @@ -1852,17 +1851,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - @Test - public void testDeleteReturnsOperationOutcome() { - Patient p = new Patient(); - p.addName().setFamily("FAM"); - IIdType id = myClient.create().resource(p).execute().getId().toUnqualifiedVersionless(); - - MethodOutcome resp = myClient.delete().resourceById(id).execute(); - OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); - } - @Test public void testDeleteNonExistingResourceReturnsOperationOutcome() { String resourceType = "Patient"; @@ -1881,7 +1869,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { MethodOutcome resp = myClient.delete().resourceById(id).execute(); OperationOutcome oo = (OperationOutcome) resp.getOperationOutcome(); - assertThat(oo.getIssueFirstRep().getDiagnostics(), startsWith("Successfully deleted 1 resource(s) in ")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("Successfully deleted 1 resource(s).")); + assertThat(oo.getIssueFirstRep().getDiagnostics(), containsString("Took ")); resp = myClient.delete().resourceById(id).execute(); oo = (OperationOutcome) resp.getOperationOutcome(); @@ -2349,7 +2338,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertThat(ids, not(containsInRelativeOrder(c3Id))); } - @Test public void testEverythingPatientTypeWithIdParameter() { String methodName = "testEverythingPatientTypeWithIdParameter"; @@ -2967,7 +2955,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } - @Test public void testValidateResourceContainingProfileDeclarationDoesntResolve() throws IOException { Observation input = new Observation(); @@ -2988,7 +2975,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } - @SuppressWarnings("unused") @Test public void testFullTextSearch() throws Exception { @@ -3397,31 +3383,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - // private void delete(String theResourceType, String theParamName, String theParamValue) { - // Bundle resources; - // do { - // IQuery forResource = ourClient.search().forResource(theResourceType); - // if (theParamName != null) { - // forResource = forResource.where(new StringClientParam(theParamName).matches().value(theParamValue)); - // } - // resources = forResource.execute(); - // for (IResource next : resources.toListOfResources()) { - // ourLog.info("Deleting resource: {}", next.getId()); - // ourClient.delete().resource(next).execute(); - // } - // } while (resources.size() > 0); - // } - // - // private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) - // { - // Bundle resources = ourClient.search().forResource(theResourceType).where(new - // TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute(); - // for (IResource next : resources.toListOfResources()) { - // ourLog.info("Deleting resource: {}", next.getId()); - // ourClient.delete().resource(next).execute(); - // } - // } - @Test public void testIdAndVersionInBodyForCreate() throws IOException { String methodName = "testIdAndVersionInBodyForCreate"; @@ -3464,6 +3425,31 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } + // private void delete(String theResourceType, String theParamName, String theParamValue) { + // Bundle resources; + // do { + // IQuery forResource = ourClient.search().forResource(theResourceType); + // if (theParamName != null) { + // forResource = forResource.where(new StringClientParam(theParamName).matches().value(theParamValue)); + // } + // resources = forResource.execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } while (resources.size() > 0); + // } + // + // private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) + // { + // Bundle resources = ourClient.search().forResource(theResourceType).where(new + // TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute(); + // for (IResource next : resources.toListOfResources()) { + // ourLog.info("Deleting resource: {}", next.getId()); + // ourClient.delete().resource(next).execute(); + // } + // } + @Test public void testIdAndVersionInBodyForUpdate() throws IOException { String methodName = "testIdAndVersionInBodyForUpdate"; @@ -4190,7 +4176,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals("hugs", enc.getReasonCodeFirstRep().getCodingFirstRep().getCode()); } - @Test public void testTerminologyWithCompleteCs_SearchForConceptIn() throws Exception { @@ -5093,7 +5078,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(2, ids.size()); } - @Test public void testSearchWithNormalizedQuantitySearchSupported_DegreeFahrenheit() throws Exception { @@ -5244,7 +5228,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } - @Test public void testSearchReusesResultsDisabled() { List resources = new ArrayList<>(); @@ -5863,7 +5846,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertEquals(oid2, list.get(3)); } - @Test public void testSearchWithMissing() { myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED); @@ -7475,7 +7457,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // ensure the patient has the expected overall history Bundle result = myClient.history() - .onInstance("Patient/"+patientId) + .onInstance("Patient/" + patientId) .returnBundle(Bundle.class) .execute(); @@ -7508,8 +7490,8 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/1")); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/2")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); } private void verifyAtBehaviourWhenQueriedDateAfterTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -7518,17 +7500,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.after(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); assertEquals(1, resultIds.size()); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/2")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); } private void verifyAtBehaviourWhenQueriedDateBeforeTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { - Date timeBetweenUpdates = DateUtils.addMilliseconds(dateV1, - delayInMs); + Date timeBetweenUpdates = DateUtils.addMilliseconds(dateV1, -delayInMs); assertTrue(timeBetweenUpdates.before(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_at=gt" + toStr(timeBetweenUpdates)); assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/1")); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/2")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); } private void verifySinceBehaviourWhenQueriedDateDuringTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -7537,7 +7519,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_since=" + toStr(timeBetweenUpdates)); assertEquals(1, resultIds.size()); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/2")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); } private void verifySinceBehaviourWhenQueriedDateAfterTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { @@ -7549,13 +7531,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } private void verifySinceBehaviourWhenQueriedDateBeforeTwoUpdatedDates(Long patientId, int delayInMs, Date dateV1, Date dateV2) throws IOException { - Date timeBetweenUpdates = DateUtils.addMilliseconds(dateV1, - delayInMs); + Date timeBetweenUpdates = DateUtils.addMilliseconds(dateV1, -delayInMs); assertTrue(timeBetweenUpdates.before(dateV1)); assertTrue(timeBetweenUpdates.before(dateV2)); List resultIds = searchAndReturnUnqualifiedIdValues(myServerBase + "/Patient/" + patientId + "/_history?_since=" + toStr(timeBetweenUpdates)); assertEquals(2, resultIds.size()); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/1")); - assertTrue(resultIds.contains("Patient/"+ patientId +"/_history/2")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/1")); + assertTrue(resultIds.contains("Patient/" + patientId + "/_history/2")); } @Test @@ -7686,7 +7668,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { return new InstantDt(theDate).getValueAsString(); } - public IIdType createPatientWithIndexAtOrganization(String theMethodName, String theIndex, IIdType theOrganizationId) { Patient p1 = new Patient(); p1.addName().setFamily(theMethodName + theIndex); @@ -7728,39 +7709,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { @Nested public class MissingSearchParameterTests { - private interface XtoY { - Y doTask(X theInput); - } - - private static class MissingSearchTestParameters { - /** - * The setting for IndexMissingFields - */ - public final DaoConfig.IndexEnabledEnum myEnableMissingFieldsValue; - - /** - * Whether to use :missing=true/false - */ - public final boolean myIsMissing; - - /** - * Whether or not the field is populated or not. - * True -> populate field. - * False -> not populated - */ - public final boolean myIsValuePresentOnResource; - - public MissingSearchTestParameters( - DaoConfig.IndexEnabledEnum theEnableMissingFields, - boolean theIsMissing, - boolean theHasField - ) { - myEnableMissingFieldsValue = theEnableMissingFields; - myIsMissing = theIsMissing; - myIsValuePresentOnResource = theHasField; - } - } - private IParser myParser; @BeforeEach @@ -7827,30 +7775,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { //@formatter:on } - /** - * The method that generates parameters for tests - */ - private static Stream provideParameters() { - return Stream.of( - // 1 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, true, true)), - // 2 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, false, false)), - // 3 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, false, true)), - // 4 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, true, false)), - // 5 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, true, true)), - // 6 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, false, true)), - // 7 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, true, false)), - // 8 - Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, false, false)) - ); - } - /** * Runs the actual test for whichever search parameter and given inputs we want. */ @@ -8036,6 +7960,63 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ); }); } + + private interface XtoY { + Y doTask(X theInput); + } + + private static class MissingSearchTestParameters { + /** + * The setting for IndexMissingFields + */ + public final DaoConfig.IndexEnabledEnum myEnableMissingFieldsValue; + + /** + * Whether to use :missing=true/false + */ + public final boolean myIsMissing; + + /** + * Whether or not the field is populated or not. + * True -> populate field. + * False -> not populated + */ + public final boolean myIsValuePresentOnResource; + + public MissingSearchTestParameters( + DaoConfig.IndexEnabledEnum theEnableMissingFields, + boolean theIsMissing, + boolean theHasField + ) { + myEnableMissingFieldsValue = theEnableMissingFields; + myIsMissing = theIsMissing; + myIsValuePresentOnResource = theHasField; + } + } + + /** + * The method that generates parameters for tests + */ + private static Stream provideParameters() { + return Stream.of( + // 1 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, true, true)), + // 2 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, false, false)), + // 3 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, false, true)), + // 4 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.ENABLED, true, false)), + // 5 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, true, true)), + // 6 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, false, true)), + // 7 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, true, false)), + // 8 + Arguments.of(new MissingSearchTestParameters(DaoConfig.IndexEnabledEnum.DISABLED, false, false)) + ); + } } } 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 1bc194ce46d..88096d22434 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,6 +17,7 @@ import ca.uhn.fhir.jpa.cache.ResourceChangeListenerCacheRefresherImpl; 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.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.JpaResourceDao; import ca.uhn.fhir.jpa.dao.TransactionProcessor; import ca.uhn.fhir.jpa.dao.data.IResourceHistoryTableDao; @@ -142,6 +143,8 @@ public class GiantTransactionPerfTest { private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer; @Mock private IIdHelperService myIdHelperService; + @Mock + private IJpaStorageResourceParser myJpaStorageResourceParser; @AfterEach public void afterEach() { @@ -192,7 +195,6 @@ public class GiantTransactionPerfTest { mySystemDao = new FhirSystemDaoR4(); mySystemDao.setTransactionProcessorForUnitTest(myTransactionProcessor); mySystemDao.setDaoConfigForUnitTest(myDaoConfig); - mySystemDao.setPartitionSettingsForUnitTest(myPartitionSettings); mySystemDao.start(); when(myAppCtx.getBean(eq(IInstanceValidatorModule.class))).thenReturn(myInstanceValidatorSvc); @@ -265,6 +267,7 @@ public class GiantTransactionPerfTest { myEobDao.setDaoConfigForUnitTest(myDaoConfig); myEobDao.setIdHelperSvcForUnitTest(myIdHelperService); myEobDao.setPartitionSettingsForUnitTest(myPartitionSettings); + myEobDao.setJpaStorageResourceParserForUnitTest(myJpaStorageResourceParser); myEobDao.start(); myDaoRegistry.setResourceDaos(Lists.newArrayList(myEobDao)); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ITermReadSvcTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ITermReadSvcTest.java index 3c9a0ee995b..a9610ed7f54 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ITermReadSvcTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/term/ITermReadSvcTest.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider; import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; import ca.uhn.fhir.jpa.dao.data.ITermValueSetDao; import ca.uhn.fhir.jpa.entity.TermConcept; import ca.uhn.fhir.jpa.model.entity.ResourceTable; @@ -85,6 +86,8 @@ class ITermReadSvcTest { private DaoRegistry myDaoRegistry; @Mock private IFhirResourceDao myFhirResourceDao; + @Mock + private IJpaStorageResourceParser myJpaStorageResourceParser; @Nested @@ -93,6 +96,7 @@ class ITermReadSvcTest { @BeforeEach public void setup() { ReflectionTestUtils.setField(testedClass, "myTermValueSetDao", myTermValueSetDao); + ReflectionTestUtils.setField(testedClass, "myJpaStorageResourceParser", myJpaStorageResourceParser); } @Test @@ -214,6 +218,7 @@ class ITermReadSvcTest { @BeforeEach public void setup() { ReflectionTestUtils.setField(testedClass, "myEntityManager", myEntityManager); + ReflectionTestUtils.setField(testedClass, "myJpaStorageResourceParser", myJpaStorageResourceParser); } @@ -245,13 +250,13 @@ class ITermReadSvcTest { when(myEntityManager.createQuery(anyString()).getResultList()) .thenReturn(Lists.newArrayList(resource1)); when(myDaoRegistry.getResourceDao("CodeSystem")).thenReturn(myFhirResourceDao); - when(myFhirResourceDao.toResource(resource1, false)).thenReturn(myCodeSystemResource); + when(myJpaStorageResourceParser.toResource(resource1, false)).thenReturn(myCodeSystemResource); testedClass.readCodeSystemByForcedId("a-cs-id"); - verify(myFhirResourceDao, times(1)).toResource(any(), eq(false)); + verify(myJpaStorageResourceParser, times(1)).toResource(any(), eq(false)); } } diff --git a/hapi-fhir-jpaserver-test-r4b/pom.xml b/hapi-fhir-jpaserver-test-r4b/pom.xml index 77e75be98f6..c9a374220a0 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.3.1-SNAPSHOT + 6.3.2-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 d0c8c2d8971..646ea335bc8 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.3.1-SNAPSHOT + 6.3.2-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 2fb8f88c176..7c61d62309d 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java index 95aa4261ed8..de2b7741bd1 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -535,12 +535,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil myPartitionSettings.setPartitioningEnabled(false); } - @Order(Integer.MIN_VALUE) - @BeforeEach - public void beforeResetInterceptors() { - // nothing - } - @Override @Order(Integer.MAX_VALUE) @AfterEach diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/PatientReindexTestHelper.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/PatientReindexTestHelper.java index a0385253d91..267e84de563 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/PatientReindexTestHelper.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/PatientReindexTestHelper.java @@ -146,8 +146,8 @@ public class PatientReindexTestHelper { patient.getNameFirstRep().setFamily("Family-"+i).addGiven("Given-"+i); patient.getIdentifierFirstRep().setValue("Id-"+i); myPatientDao.create(patient, requestDetails); + TestUtil.sleepOneClick(); } - TestUtil.sleepOneClick(); } private void validatePersistedPatients(int theExpectedNumPatients, long theExpectedVersion) { diff --git a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/term/LoincFullLoadR4SandboxIT.java b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/term/LoincFullLoadR4SandboxIT.java index 1a8877d9ccc..6a3e342753e 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/term/LoincFullLoadR4SandboxIT.java +++ b/hapi-fhir-jpaserver-test-utilities/src/test/java/ca/uhn/fhir/jpa/term/LoincFullLoadR4SandboxIT.java @@ -2,6 +2,8 @@ package ca.uhn.fhir.jpa.term; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDaoValueSet; +import ca.uhn.fhir.jpa.dao.IJpaStorageResourceParser; +import ca.uhn.fhir.jpa.dao.IStorageResourceParser; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemDao; import ca.uhn.fhir.jpa.dao.data.ITermCodeSystemVersionDao; import ca.uhn.fhir.jpa.dao.data.ITermConceptDao; @@ -192,6 +194,8 @@ public class LoincFullLoadR4SandboxIT extends BaseJpaTest { private int askAtOrderEntryCount = 0; private int validatedPropertiesCounter = 0; private int validatedMapToEntriesCounter = 0; + @Autowired + private IJpaStorageResourceParser myJpaStorageResourceParser; @BeforeEach void setUp() { @@ -606,7 +610,7 @@ public class LoincFullLoadR4SandboxIT extends BaseJpaTest { List vsList = (List) q1.getResultList(); assertEquals(1, vsList.size()); long vsLongId = vsList.get(0).getId(); - ValueSet vs = (ValueSet) myValueSetDao.toResource(vsList.get(0), false); + ValueSet vs = (ValueSet) myJpaStorageResourceParser.toResource(vsList.get(0), false); assertNotNull(vs); Query q2 = myEntityManager.createQuery("from TermValueSet where myResource = " + vsLongId); diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index e0a9b637dfc..89e64e9ff83 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 74f23de9987..3850ac69145 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 6a3f7b9d826..bfc77612bd4 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index 4b6c5c3cbe0..51cd8dc0cb2 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/storage/TransactionDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/storage/TransactionDetails.java index 12b39e4c71e..f08bcd7f209 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/storage/TransactionDetails.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/storage/TransactionDetails.java @@ -63,6 +63,7 @@ public class TransactionDetails { private Map myUserData; private ListMultimap myDeferredInterceptorBroadcasts; private EnumSet myDeferredInterceptorBroadcastPointcuts; + private boolean myFhirTransaction; /** * Constructor @@ -306,5 +307,13 @@ public class TransactionDetails { public boolean hasResolvedResourceIds() { return !myResolvedResourceIds.isEmpty(); } + + public void setFhirTransaction(boolean theFhirTransaction) { + myFhirTransaction = theFhirTransaction; + } + + public boolean isFhirTransaction() { + return myFhirTransaction; + } } 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 0572877a1f7..00e8d13f19c 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 @@ -37,6 +37,7 @@ import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.hl7.fhir.instance.model.api.IBaseBundle; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -515,6 +516,9 @@ public class AuthorizationInterceptor implements IRuleApplier { retVal = retVal.subList(1, retVal.size()); } + // Don't apply security to OperationOutcome + retVal.removeIf(t->t instanceof IBaseOperationOutcome); + return retVal; } diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml index a65f17bb172..41f33d8043c 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.3.1-SNAPSHOT + 6.3.2-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 636eb0b1395..4991094fb74 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml @@ -20,7 +20,7 @@ ca.uhn.hapi.fhir hapi-fhir-caching-api - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT com.github.ben-manes.caffeine diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml index d3c3b57b4c9..9c6914ff64d 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.3.1-SNAPSHOT + 6.3.2-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 0b72941b471..6d897a66caf 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../../pom.xml diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml index c8b7cfdd29d..73b6bb4a201 100644 --- a/hapi-fhir-serviceloaders/pom.xml +++ b/hapi-fhir-serviceloaders/pom.xml @@ -5,7 +5,7 @@ hapi-fhir ca.uhn.hapi.fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../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 b1b956e3e58..fbac1fe3288 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.3.1-SNAPSHOT + 6.3.2-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 dbf20aa5813..24c99d01a8c 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.3.1-SNAPSHOT + 6.3.2-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 e977907e8bb..0bb0ebbe371 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT hapi-fhir-spring-boot-sample-client-okhttp 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 8cb30c1b1c7..561d3d9d61a 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT hapi-fhir-spring-boot-sample-server-jersey 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 108d95fd6db..4b5863459bf 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT hapi-fhir-spring-boot-samples 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 0b9ace3ed81..5ca1f2f6acd 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index 29650eb9a7b..fc06349e39f 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml index 680c21c7726..7c68308f295 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.3.1-SNAPSHOT + 6.3.2-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 fe46883cf68..f209458fedf 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java index 4ae86254af5..c50246c5aa0 100644 --- a/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java +++ b/hapi-fhir-storage-batch2-jobs/src/main/java/ca/uhn/fhir/batch2/jobs/export/ExpandResourcesStep.java @@ -33,6 +33,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.api.dao.DaoRegistry; import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; import ca.uhn.fhir.jpa.bulk.export.api.IBulkExportProcessor; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.interceptor.ResponseTerminologyTranslationSvc; @@ -41,8 +42,10 @@ import com.google.common.collect.ListMultimap; import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; import javax.annotation.Nonnull; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; @@ -60,8 +63,13 @@ public class ExpandResourcesStep implements IJobStepWorker ca.uhn.hapi.fhir hapi-deployable-pom - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml index 6dfab8ef21b..a1850eabad6 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml index d1aef29fb8d..b96b56d36b5 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml 4.0.0 diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml index ebc8f3f25eb..1499436d0e4 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java index 29bd8ffaedb..98517e43f69 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IDao.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.jpa.api.dao; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.BaseHasResource; import ca.uhn.fhir.jpa.model.entity.IBaseResourceEntity; import ca.uhn.fhir.jpa.model.entity.ResourceTag; @@ -42,8 +43,5 @@ public interface IDao { FhirContext getContext(); - IBaseResource toResource(BaseHasResource theEntity, boolean theForHistoryOperation); - - R toResource(Class theResourceType, IBaseResourceEntity theEntity, Collection theTagList, boolean theForHistoryOperation); } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java index 5c79bde22ec..a8c93fc8786 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IFhirResourceDao.java @@ -127,6 +127,7 @@ public interface IFhirResourceDao extends IDao { ExpungeOutcome forceExpungeInExistingTransaction(IIdType theId, ExpungeOptions theExpungeOptions, RequestDetails theRequest); + @Nonnull Class getResourceType(); IBundleProvider history(Date theSince, Date theUntil, Integer theOffset, RequestDetails theRequestDetails); @@ -167,8 +168,16 @@ public interface IFhirResourceDao extends IDao { */ MT metaGetOperation(Class theType, RequestDetails theRequestDetails); + /** + * Opens a new transaction and performs a patch operation + */ DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequestDetails); + /** + * Execute a patch operation within the existing database transaction + */ + DaoMethodOutcome patchInTransaction(IIdType theId, String theConditionalUrl, boolean thePerformIndexing, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails); + /** * Read a resource - Note that this variant of the method does not take in a {@link RequestDetails} and * therefore can not fire any interceptors. diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java index 605d9c9dbcf..ca624a758b3 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/dao/IJpaDao.java @@ -20,7 +20,9 @@ package ca.uhn.fhir.jpa.api.dao; * #L% */ +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -41,13 +43,15 @@ public interface IJpaDao { boolean theForceUpdate, boolean theCreateNewHistoryEntry); - IBasePersistedResource updateInternal( + DaoMethodOutcome updateInternal( RequestDetails theRequestDetails, T theResource, + String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, IBasePersistedResource theEntity, IIdType theResourceId, IBaseResource theOldResource, + RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails); } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java index f52272dc754..ae14d1bf7cd 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.api.model; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -31,6 +32,8 @@ public class DaoMethodOutcome extends MethodOutcome { private IBaseResource myPreviousResource; private boolean myNop; private ResourcePersistentId myResourcePersistentId; + private RestOperationTypeEnum myOperationType; + private String myMatchUrl; /** * Constructor @@ -39,6 +42,22 @@ public class DaoMethodOutcome extends MethodOutcome { super(); } + public RestOperationTypeEnum getOperationType() { + return myOperationType; + } + + public void setOperationType(RestOperationTypeEnum theOperationType) { + myOperationType = theOperationType; + } + + public String getMatchUrl() { + return myMatchUrl; + } + + public void setMatchUrl(String theMatchUrl) { + myMatchUrl = theMatchUrl; + } + /** * Was this a NO-OP - Typically because of an update to a resource that already matched the contents provided */ diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java index 54a99b8e64f..531105a446f 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java @@ -40,6 +40,7 @@ import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.rest.api.QualifiedParamList; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; import ca.uhn.fhir.rest.api.server.IPreResourceShowDetails; import ca.uhn.fhir.rest.api.server.RequestDetails; @@ -60,6 +61,9 @@ import ca.uhn.fhir.util.BundleUtil; import ca.uhn.fhir.util.FhirTerser; import ca.uhn.fhir.util.OperationOutcomeUtil; import ca.uhn.fhir.util.ResourceReferenceInfo; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; +import ca.uhn.fhir.util.StopWatch; +import ca.uhn.fhir.util.UrlUtil; import com.google.common.annotations.VisibleForTesting; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; @@ -67,6 +71,8 @@ import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.InstantType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @@ -85,6 +91,8 @@ import static org.apache.commons.lang3.StringUtils.defaultString; import static org.apache.commons.lang3.StringUtils.isNotBlank; public abstract class BaseStorageDao { + private static final Logger ourLog = LoggerFactory.getLogger(BaseStorageDao.class); + public static final String OO_SEVERITY_ERROR = "error"; public static final String OO_SEVERITY_INFO = "information"; public static final String OO_SEVERITY_WARN = "warning"; @@ -249,8 +257,11 @@ public abstract class BaseStorageDao { } } - protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) { - DaoMethodOutcome outcome = new DaoMethodOutcome().setPersistentId(theEntity.getPersistentId()); + protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource, @Nullable String theMatchUrl, @Nonnull RestOperationTypeEnum theOperationType) { + DaoMethodOutcome outcome = new DaoMethodOutcome(); + outcome.setPersistentId(theEntity.getPersistentId()); + outcome.setMatchUrl(theMatchUrl); + outcome.setOperationType(theOperationType); if (theEntity instanceof ResourceTable) { if (((ResourceTable) theEntity).isUnchangedInCurrentOperation()) { @@ -361,12 +372,28 @@ public abstract class BaseStorageDao { } public IBaseOperationOutcome createInfoOperationOutcome(String theMessage) { - return createOperationOutcome(OO_SEVERITY_INFO, theMessage, "informational"); + return createInfoOperationOutcome(theMessage, null); + } + + public IBaseOperationOutcome createInfoOperationOutcome(String theMessage, @Nullable StorageResponseCodeEnum theStorageResponseCode) { + return createOperationOutcome(OO_SEVERITY_INFO, theMessage, "informational", theStorageResponseCode); } private IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) { + return createOperationOutcome(theSeverity, theMessage, theCode, null); + } + + protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode, @Nullable StorageResponseCodeEnum theStorageResponseCode) { IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); - OperationOutcomeUtil.addIssue(getContext(), oo, theSeverity, theMessage, null, theCode); + String detailSystem = null; + String detailCode = null; + String detailDescription = null; + if (theStorageResponseCode != null) { + detailSystem = theStorageResponseCode.getSystem(); + detailCode = theStorageResponseCode.getCode(); + detailDescription = theStorageResponseCode.getDisplay(); + } + OperationOutcomeUtil.addIssue(getContext(), oo, theSeverity, theMessage, null, theCode, detailSystem, detailCode, detailDescription); return oo; } @@ -377,18 +404,17 @@ public abstract class BaseStorageDao { * * @param theResourceId - the id of the object being deleted. Eg: Patient/123 */ - protected DaoMethodOutcome createMethodOutcomeForResourceId(String theResourceId, String theMessageKey) { + protected DaoMethodOutcome createMethodOutcomeForResourceId(String theResourceId, String theMessageKey, StorageResponseCodeEnum theStorageResponseCode) { DaoMethodOutcome outcome = new DaoMethodOutcome(); IIdType id = getContext().getVersion().newIdType(); id.setValue(theResourceId); outcome.setId(id); - IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getContext()); String message = getContext().getLocalizer().getMessage(BaseStorageDao.class, theMessageKey, id); String severity = "information"; String code = "informational"; - OperationOutcomeUtil.addIssue(getContext(), oo, severity, message, null, code); + IBaseOperationOutcome oo = createOperationOutcome(severity, message, code, theStorageResponseCode); outcome.setOperationOutcome(oo); return outcome; @@ -451,6 +477,83 @@ public abstract class BaseStorageDao { } } + + protected void populateOperationOutcomeForUpdate(@Nullable StopWatch theItemStopwatch, DaoMethodOutcome theMethodOutcome, String theMatchUrl, RestOperationTypeEnum theOperationType) { + String msg; + StorageResponseCodeEnum outcome; + + if (theOperationType == RestOperationTypeEnum.PATCH) { + + if (theMatchUrl != null) { + if (theMethodOutcome.isNop()) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulPatchConditionalNoChange", theMethodOutcome.getId(), UrlUtil.sanitizeUrlPart(theMatchUrl), theMethodOutcome.getId()); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_CONDITIONAL_PATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulPatchConditional", theMethodOutcome.getId(), UrlUtil.sanitizeUrlPart(theMatchUrl), theMethodOutcome.getId()); + } + } else { + if (theMethodOutcome.isNop()) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_PATCH_NO_CHANGE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulPatchNoChange", theMethodOutcome.getId()); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_PATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulPatch", theMethodOutcome.getId()); + } + } + + } else if (theOperationType == RestOperationTypeEnum.CREATE) { + + if (theMatchUrl == null) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_CREATE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreate", theMethodOutcome.getId()); + } else if (theMethodOutcome.isNop()) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalWithMatch", theMethodOutcome.getId(), UrlUtil.sanitizeUrlPart(theMatchUrl)); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_CREATE_NO_CONDITIONAL_MATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalNoMatch", theMethodOutcome.getId(), UrlUtil.sanitizeUrlPart(theMatchUrl)); + } + + } else if (theMethodOutcome.isNop()) { + + if (theMatchUrl != null) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdateConditionalNoChangeWithMatch", theMethodOutcome.getId(), theMatchUrl); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CHANGE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdateNoChange", theMethodOutcome.getId()); + } + + } else { + + if (theMatchUrl != null) { + if (theMethodOutcome.getCreated() == Boolean.TRUE) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdateConditionalNoMatch", theMethodOutcome.getId()); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdateConditionalWithMatch", theMethodOutcome.getId(), theMatchUrl); + } + } else if (theMethodOutcome.getCreated() == Boolean.TRUE) { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE_AS_CREATE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdateAsCreate", theMethodOutcome.getId()); + } else { + outcome = StorageResponseCodeEnum.SUCCESSFUL_UPDATE; + msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulUpdate", theMethodOutcome.getId()); + } + + } + + if (theItemStopwatch != null) { + String msgSuffix = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulTimingSuffix", theItemStopwatch.getMillis()); + msg = msg + " " + msgSuffix; + } + + theMethodOutcome.setOperationOutcome(createInfoOperationOutcome(msg, outcome)); + ourLog.debug(msg); + } + /** * @see ModelConfig#getAutoVersionReferenceAtPaths() */ diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageResourceDao.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageResourceDao.java new file mode 100644 index 00000000000..ffd3ec75623 --- /dev/null +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageResourceDao.java @@ -0,0 +1,207 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR Storage api + * %% + * Copyright (C) 2014 - 2022 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% + */ + +import ca.uhn.fhir.i18n.Msg; +import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao; +import ca.uhn.fhir.jpa.api.dao.IJpaDao; +import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import ca.uhn.fhir.jpa.patch.FhirPatch; +import ca.uhn.fhir.jpa.patch.JsonPatchUtils; +import ca.uhn.fhir.jpa.patch.XmlPatchUtils; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.api.PatchTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.api.server.RequestDetails; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; +import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; +import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException; +import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import org.hl7.fhir.instance.model.api.IBaseParameters; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.support.TransactionSynchronizationManager; + +import javax.annotation.Nonnull; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +public abstract class BaseStorageResourceDao extends BaseStorageDao implements IFhirResourceDao, IJpaDao { + public static final StrictErrorHandler STRICT_ERROR_HANDLER = new StrictErrorHandler(); + + @Autowired + protected abstract HapiTransactionService getTransactionService(); + + @Autowired + protected abstract MatchResourceUrlService getMatchResourceUrlService(); + + @Autowired + protected abstract IStorageResourceParser getStorageResourceParser(); + + @Override + public DaoMethodOutcome patch(IIdType theId, String theConditionalUrl, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequestDetails) { + TransactionDetails transactionDetails = new TransactionDetails(); + return getTransactionService().execute(theRequestDetails, transactionDetails, tx -> patchInTransaction(theId, theConditionalUrl, true, thePatchType, thePatchBody, theFhirPatchBody, theRequestDetails, transactionDetails)); + } + + @Override + public DaoMethodOutcome patchInTransaction(IIdType theId, String theConditionalUrl, boolean thePerformIndexing, PatchTypeEnum thePatchType, String thePatchBody, IBaseParameters theFhirPatchBody, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) { + assert TransactionSynchronizationManager.isActualTransactionActive(); + + IBasePersistedResource entityToUpdate; + IIdType resourceId; + if (isNotBlank(theConditionalUrl)) { + + Set match = getMatchResourceUrlService().processMatchUrl(theConditionalUrl, getResourceType(), theTransactionDetails, theRequestDetails); + if (match.size() > 1) { + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "PATCH", theConditionalUrl, match.size()); + throw new PreconditionFailedException(Msg.code(972) + msg); + } else if (match.size() == 1) { + ResourcePersistentId pid = match.iterator().next(); + entityToUpdate = readEntityLatestVersion(pid, theRequestDetails, theTransactionDetails); + resourceId = entityToUpdate.getIdDt(); + } else { + String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "invalidMatchUrlNoMatches", theConditionalUrl); + throw new ResourceNotFoundException(Msg.code(973) + msg); + } + + } else { + resourceId = theId; + entityToUpdate = readEntityLatestVersion(theId, theRequestDetails, theTransactionDetails); + if (theId.hasVersionIdPart()) { + if (theId.getVersionIdPartAsLong() != entityToUpdate.getVersion()) { + throw new ResourceVersionConflictException(Msg.code(974) + "Version " + theId.getVersionIdPart() + " is not the most recent version of this resource, unable to apply patch"); + } + } + } + + validateResourceType(entityToUpdate, getResourceName()); + + if (entityToUpdate.isDeleted()) { + throw createResourceGoneException(entityToUpdate); + } + + IBaseResource resourceToUpdate = getStorageResourceParser().toResource(entityToUpdate, false); + IBaseResource destination; + switch (thePatchType) { + case JSON_PATCH: + destination = JsonPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); + break; + case XML_PATCH: + destination = XmlPatchUtils.apply(getContext(), resourceToUpdate, thePatchBody); + break; + case FHIR_PATCH_XML: + case FHIR_PATCH_JSON: + default: + IBaseParameters fhirPatchJson = theFhirPatchBody; + new FhirPatch(getContext()).apply(resourceToUpdate, fhirPatchJson); + destination = resourceToUpdate; + break; + } + + @SuppressWarnings("unchecked") + T destinationCasted = (T) destination; + myFhirContext.newJsonParser().setParserErrorHandler(STRICT_ERROR_HANDLER).encodeResourceToString(destinationCasted); + + preProcessResourceForStorage(destinationCasted, theRequestDetails, theTransactionDetails, true); + + return doUpdateForUpdateOrPatch(theRequestDetails, resourceId, theConditionalUrl, thePerformIndexing, false, destinationCasted, entityToUpdate, RestOperationTypeEnum.PATCH, theTransactionDetails); + } + + @Override + @Nonnull + public abstract Class getResourceType(); + + @Override + @Nonnull + protected abstract String getResourceName(); + + protected abstract IBasePersistedResource readEntityLatestVersion(ResourcePersistentId thePersistentId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails); + + protected abstract IBasePersistedResource readEntityLatestVersion(IIdType theId, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails); + + + protected DaoMethodOutcome doUpdateForUpdateOrPatch(RequestDetails theRequest, IIdType theResourceId, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, T theResource, IBasePersistedResource theEntity, RestOperationTypeEnum theOperationType, TransactionDetails theTransactionDetails) { + if (theResourceId.hasVersionIdPart() && Long.parseLong(theResourceId.getVersionIdPart()) != theEntity.getVersion()) { + throw new ResourceVersionConflictException(Msg.code(989) + "Trying to update " + theResourceId + " but this is not the current version"); + } + + if (theResourceId.hasResourceType() && !theResourceId.getResourceType().equals(getResourceName())) { + throw new UnprocessableEntityException(Msg.code(990) + "Invalid resource ID[" + theEntity.getIdDt().toUnqualifiedVersionless() + "] of type[" + theEntity.getResourceType() + "] - Does not match expected [" + getResourceName() + "]"); + } + + IBaseResource oldResource; + if (getConfig().isMassIngestionMode()) { + oldResource = null; + } else { + oldResource = getStorageResourceParser().toResource(theEntity, false); + } + + /* + * Mark the entity as not deleted - This is also done in the actual updateInternal() + * method later on so it usually doesn't matter whether we do it here, but in the + * case of a transaction with multiple PUTs we don't get there until later so + * having this here means that a transaction can have a reference in one + * resource to another resource in the same transaction that is being + * un-deleted by the transaction. Wacky use case, sure. But it's real. + * + * See SystemProviderR4Test#testTransactionReSavesPreviouslyDeletedResources + * for a test that needs this. + */ + boolean wasDeleted = theEntity.isDeleted(); + theEntity.setNotDeleted(); + + /* + * If we aren't indexing, that means we're doing this inside a transaction. + * The transaction will do the actual storage to the database a bit later on, + * after placeholder IDs have been replaced, by calling {@link #updateInternal} + * directly. So we just bail now. + */ + if (!thePerformIndexing) { + theResource.setId(theEntity.getIdDt().getValue()); + DaoMethodOutcome outcome = toMethodOutcome(theRequest, theEntity, theResource, theMatchUrl, theOperationType).setCreated(wasDeleted); + outcome.setPreviousResource(oldResource); + if (!outcome.isNop()) { + // Technically this may not end up being right since we might not increment if the + // contents turn out to be the same + outcome.setId(outcome.getId().withVersion(Long.toString(outcome.getId().getVersionIdPartAsLong() + 1))); + } + return outcome; + } + + /* + * Otherwise, we're not in a transaction + */ + return updateInternal(theRequest, theResource, theMatchUrl, thePerformIndexing, theForceUpdateVersion, theEntity, theResourceId, oldResource, theOperationType, theTransactionDetails); + } + + public static void validateResourceType(IBasePersistedResource theEntity, String theResourceName) { + if (!theResourceName.equals(theEntity.getResourceType())) { + throw new ResourceNotFoundException(Msg.code(935) + "Resource with ID " + theEntity.getIdDt().getIdPart() + " exists but it is not of type " + theResourceName + ", found resource of type " + theEntity.getResourceType()); + } + } +} 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 620b6236204..f4b8aec24fd 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 @@ -56,6 +56,7 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.PatchTypeEnum; import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts; import ca.uhn.fhir.rest.api.server.storage.TransactionDetails; @@ -272,7 +273,6 @@ public abstract class BaseTransactionProcessor { if(shouldSwapBinaryToActualResource(theRes, theResourceType, nextResourceId)) { theRes = idToPersistedOutcome.get(newId).getResource(); - theResourceType = idToPersistedOutcome.get(newId).getResource().fhirType(); } if (outcome.getCreated()) { @@ -280,9 +280,14 @@ public abstract class BaseTransactionProcessor { } else { myVersionAdapter.setResponseStatus(newEntry, toStatusString(Constants.STATUS_HTTP_200_OK)); } + Date lastModified = getLastModified(theRes); myVersionAdapter.setResponseLastModified(newEntry, lastModified); + if (outcome.getOperationOutcome() != null) { + myVersionAdapter.setResponseOutcome(newEntry, outcome.getOperationOutcome()); + } + if (theRequestDetails != null) { String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER); PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(null, prefer).getReturn(); @@ -446,6 +451,7 @@ public abstract class BaseTransactionProcessor { ourLog.debug("Beginning {} with {} resources", theActionName, numberOfEntries); final TransactionDetails transactionDetails = new TransactionDetails(); + transactionDetails.setFhirTransaction(true); final StopWatch transactionStopWatch = new StopWatch(); // Do all entries have a verb? @@ -958,7 +964,7 @@ public abstract class BaseTransactionProcessor { if (nextResourceId != null) { handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); } - entriesToProcess.put(nextRespEntry, outcome.getId()); + entriesToProcess.put(nextRespEntry, outcome.getId(), nextRespEntry); if (outcome.getCreated() == false) { nonUpdatedEntities.add(outcome.getId()); } else { @@ -981,8 +987,9 @@ public abstract class BaseTransactionProcessor { DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequest, theTransactionDetails); if (outcome.getEntity() != null) { deletedResources.add(deleteId.getValueAsString()); - entriesToProcess.put(nextRespEntry, outcome.getId()); + entriesToProcess.put(nextRespEntry, outcome.getId(), nextRespEntry); } + myVersionAdapter.setResponseOutcome(nextRespEntry, outcome.getOperationOutcome()); } } else { String matchUrl = parts.getResourceType() + '?' + parts.getParams(); @@ -1047,7 +1054,7 @@ public abstract class BaseTransactionProcessor { handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); - entriesToProcess.put(nextRespEntry, outcome.getId()); + entriesToProcess.put(nextRespEntry, outcome.getId(), nextRespEntry); break; } case "PATCH": { @@ -1092,7 +1099,7 @@ public abstract class BaseTransactionProcessor { String conditionalUrl = isNull(patchId.getIdPart()) ? url : matchUrl; - DaoMethodOutcome outcome = dao.patch(patchId, conditionalUrl, patchType, patchBody, patchBodyParameters, theRequest); + DaoMethodOutcome outcome = dao.patchInTransaction(patchId, conditionalUrl, false, patchType, patchBody, patchBodyParameters, theRequest, theTransactionDetails); setConditionalUrlToBeValidatedLater(conditionalUrlToIdMap, matchUrl, outcome.getId()); updatedEntities.add(outcome.getEntity()); if (outcome.getResource() != null) { @@ -1101,7 +1108,7 @@ public abstract class BaseTransactionProcessor { if (nextResourceId != null) { handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); } - entriesToProcess.put(nextRespEntry, outcome.getId()); + entriesToProcess.put(nextRespEntry, outcome.getId(), nextRespEntry); break; } @@ -1374,10 +1381,10 @@ public abstract class BaseTransactionProcessor { IdSubstitutionMap theIdSubstitutions, Map theIdToPersistedOutcome, EntriesToProcessMap entriesToProcess, Set nonUpdatedEntities, Set updatedEntities, FhirTerser terser, - DaoMethodOutcome nextOutcome, IBaseResource nextResource, + DaoMethodOutcome theDaoMethodOutcome, IBaseResource theResource, Set theReferencesToAutoVersion) { // References - List allRefs = terser.getAllResourceReferences(nextResource); + List allRefs = terser.getAllResourceReferences(theResource); for (ResourceReferenceInfo nextRef : allRefs) { IBaseReference resourceReference = nextRef.getResourceReference(); IIdType nextId = resourceReference.getReferenceElement(); @@ -1414,7 +1421,7 @@ public abstract class BaseTransactionProcessor { } } } else if (nextId.getValue().startsWith("urn:")) { - throw new InvalidRequestException(Msg.code(541) + "Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + nextResource.getIdElement().getResourceType()); + throw new InvalidRequestException(Msg.code(541) + "Unable to satisfy placeholder ID " + nextId.getValue() + " found in element named '" + nextRef.getName() + "' within resource of type: " + theResource.getIdElement().getResourceType()); } else { // get a map of // existing ids -> PID (for resources that exist in the DB) @@ -1454,7 +1461,7 @@ public abstract class BaseTransactionProcessor { // URIs Class> uriType = (Class>) myContext.getElementDefinition("uri").getImplementingClass(); - List> allUris = terser.getAllPopulatedChildElementsOfType(nextResource, uriType); + List> allUris = terser.getAllPopulatedChildElementsOfType(theResource, uriType); for (IPrimitiveType nextRef : allUris) { if (nextRef instanceof IIdType) { continue; // No substitution on the resource ID itself! @@ -1473,19 +1480,27 @@ public abstract class BaseTransactionProcessor { } } - IPrimitiveType deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get(nextResource); + IPrimitiveType deletedInstantOrNull; + if (theResource instanceof IAnyResource) { + deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IAnyResource) theResource); + } else { + deletedInstantOrNull = ResourceMetadataKeyEnum.DELETED_AT.get((IResource) theResource); + } Date deletedTimestampOrNull = deletedInstantOrNull != null ? deletedInstantOrNull.getValue() : null; - IFhirResourceDao dao = myDaoRegistry.getResourceDao(nextResource.getClass()); + IFhirResourceDao dao = myDaoRegistry.getResourceDao(theResource.getClass()); IJpaDao jpaDao = (IJpaDao) dao; IBasePersistedResource updateOutcome = null; - if (updatedEntities.contains(nextOutcome.getEntity())) { + if (updatedEntities.contains(theDaoMethodOutcome.getEntity())) { boolean forceUpdateVersion = !theReferencesToAutoVersion.isEmpty(); - - updateOutcome = jpaDao.updateInternal(theRequest, nextResource, true, forceUpdateVersion, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource(), theTransactionDetails); - } else if (!nonUpdatedEntities.contains(nextOutcome.getId())) { - updateOutcome = jpaDao.updateEntity(theRequest, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theTransactionDetails, false, true); + String matchUrl = theDaoMethodOutcome.getMatchUrl(); + RestOperationTypeEnum operationType = theDaoMethodOutcome.getOperationType(); + DaoMethodOutcome daoMethodOutcome = jpaDao.updateInternal(theRequest, theResource, matchUrl, true, forceUpdateVersion, theDaoMethodOutcome.getEntity(), theResource.getIdElement(), theDaoMethodOutcome.getPreviousResource(), operationType, theTransactionDetails); + updateOutcome = daoMethodOutcome.getEntity(); + theDaoMethodOutcome = daoMethodOutcome; + } else if (!nonUpdatedEntities.contains(theDaoMethodOutcome.getId())) { + updateOutcome = jpaDao.updateEntity(theRequest, theResource, theDaoMethodOutcome.getEntity(), deletedTimestampOrNull, true, false, theTransactionDetails, false, true); } // Make sure we reflect the actual final version for the resource. @@ -1497,13 +1512,18 @@ public abstract class BaseTransactionProcessor { entryId.setValue(newId.getValue()); } - nextOutcome.setId(newId); + theDaoMethodOutcome.setId(newId); IIdType target = theIdSubstitutions.getForSource(newId); if (target != null) { target.setValue(newId.getValue()); } + if (theDaoMethodOutcome.getOperationOutcome() != null) { + IBase responseEntry = entriesToProcess.getResponseBundleEntryWithVersionlessComparison(newId); + myVersionAdapter.setResponseOutcome(responseEntry, theDaoMethodOutcome.getOperationOutcome()); + } + } } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/EntriesToProcessMap.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/EntriesToProcessMap.java index 1074c5b9487..88bbab6b801 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/EntriesToProcessMap.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/EntriesToProcessMap.java @@ -34,16 +34,23 @@ public class EntriesToProcessMap { private final IdentityHashMap myEntriesToProcess = new IdentityHashMap<>(); private final Map myVersionlessIdToVersionedId = new HashMap<>(); + private final Map myVersionlessIdToResponseBundleEntry = new HashMap<>(); - public void put(IBase theBundleEntry, IIdType theId) { + public void put(IBase theBundleEntry, IIdType theId, IBase theResponseBundleEntry) { myEntriesToProcess.put(theBundleEntry, theId); - myVersionlessIdToVersionedId.put(toVersionlessValue(theId), theId); + String key = toVersionlessValue(theId); + myVersionlessIdToVersionedId.put(key, theId); + myVersionlessIdToResponseBundleEntry.put(key, theResponseBundleEntry); } public IIdType getIdWithVersionlessComparison(IIdType theId) { return myVersionlessIdToVersionedId.get(toVersionlessValue(theId)); } + public IBase getResponseBundleEntryWithVersionlessComparison(IIdType theId) { + return myVersionlessIdToResponseBundleEntry.get(toVersionlessValue(theId)); + } + public Set> entrySet() { return myEntriesToProcess.entrySet(); } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IStorageResourceParser.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IStorageResourceParser.java new file mode 100644 index 00000000000..5f0d8423a5c --- /dev/null +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/dao/IStorageResourceParser.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jpa.dao; + +/*- + * #%L + * HAPI FHIR Storage api + * %% + * Copyright (C) 2014 - 2022 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% + */ + +import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; +import org.hl7.fhir.instance.model.api.IBaseResource; + +/** + * This interface is used by any storage implementations to convert from + * persisted database format (independent of the kind of database) and + * HAPI FHIR resource model classes. + * + * Currently only DB->FHIR is enabled through this interface but the aim + * eventually is to handle both directions + */ +public interface IStorageResourceParser { + + // TODO: JA2 - Remove theForHistoryOperation flag - It toggles adding a bit of extra + // metadata but there's no reason to not always just add that, and this would + // simplify this interface + IBaseResource toResource(IBasePersistedResource theEntity, boolean theForHistoryOperation); + +} diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 1c1e6b58f52..0ff8f8f4537 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index 8f3cfeecd42..d0484a238ad 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index a8aaf72b5fe..b78eb382d59 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.3.1-SNAPSHOT + 6.3.2-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 76b09eb66f5..e2935ed5663 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 90ca6bc4664..53ad8024c57 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/BundleBuilderTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/BundleBuilderTest.java index e31bbc9fbd0..3d75b8efac8 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/BundleBuilderTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/BundleBuilderTest.java @@ -3,7 +3,11 @@ package ca.uhn.fhir.util; import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; +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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -33,6 +37,52 @@ public class BundleBuilderTest { myCheckDate = cal.getTime(); } + @Test + public void testAddEntryPatch() { + Parameters patch = new Parameters(); + Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation"); + op.addPart().setName("type").setValue(new CodeType("replace")); + op.addPart().setName("path").setValue(new CodeType("Patient.active")); + op.addPart().setName("value").setValue(new BooleanType(false)); + + BundleBuilder builder = new BundleBuilder(myFhirContext); + builder.addTransactionFhirPatchEntry(new IdType("http://foo/Patient/123"), patch); + + Bundle bundle = (Bundle) builder.getBundle(); + ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + + assertEquals(Bundle.BundleType.TRANSACTION, bundle.getType()); + assertEquals(1, bundle.getEntry().size()); + assertSame(patch, bundle.getEntry().get(0).getResource()); + assertEquals("http://foo/Patient/123", bundle.getEntry().get(0).getFullUrl()); + assertEquals("Patient/123", bundle.getEntry().get(0).getRequest().getUrl()); + assertEquals(Bundle.HTTPVerb.PATCH, bundle.getEntry().get(0).getRequest().getMethod()); + + } + + @Test + public void testAddEntryPatchConditional() { + Parameters patch = new Parameters(); + Parameters.ParametersParameterComponent op = patch.addParameter().setName("operation"); + op.addPart().setName("type").setValue(new CodeType("replace")); + op.addPart().setName("path").setValue(new CodeType("Patient.active")); + op.addPart().setName("value").setValue(new BooleanType(false)); + + BundleBuilder builder = new BundleBuilder(myFhirContext); + builder.addTransactionFhirPatchEntry(patch).conditional("Patient?identifier=http://foo|123"); + + Bundle bundle = (Bundle) builder.getBundle(); + ourLog.info("Bundle:\n{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle)); + + assertEquals(Bundle.BundleType.TRANSACTION, bundle.getType()); + assertEquals(1, bundle.getEntry().size()); + assertSame(patch, bundle.getEntry().get(0).getResource()); + assertEquals(null, bundle.getEntry().get(0).getFullUrl()); + assertEquals("Patient?identifier=http://foo|123", bundle.getEntry().get(0).getRequest().getUrl()); + assertEquals(Bundle.HTTPVerb.PATCH, bundle.getEntry().get(0).getRequest().getMethod()); + + } + @Test public void testAddEntryUpdate() { BundleBuilder builder = new BundleBuilder(myFhirContext); diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml index 225f4048856..4f44d1210ea 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index a79e1a0bbb7..5ff1042fde3 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 4cd8cce5e18..0f749e0f8d6 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index 4e7aee71759..7e6c5fff8e2 100644 --- a/hapi-fhir-testpage-overlay/pom.xml +++ b/hapi-fhir-testpage-overlay/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 57489171880..0096030acfe 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.3.1-SNAPSHOT + 6.3.2-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 55941190fad..77986685fa1 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.3.1-SNAPSHOT + 6.3.2-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 9750f4ce0dc..cbe92e1cabb 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.3.1-SNAPSHOT + 6.3.2-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 845ccd9b8c4..1f564de3054 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.3.1-SNAPSHOT + 6.3.2-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 31105bf2458..2a49023dd6e 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 483d85ebf98..157510c0567 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/src/main/resources/org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json b/hapi-fhir-validation/src/main/resources/org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json new file mode 100644 index 00000000000..b1ebcffce73 --- /dev/null +++ b/hapi-fhir-validation/src/main/resources/org/hl7/fhir/common/hapi/validation/support/HapiFhirStorageResponseCode.json @@ -0,0 +1,60 @@ +{ + "resourceType": "CodeSystem", + "url": "https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code", + "status": "active", + "copyright": "Licensed under the terms of the Apache Software License 2.0.", + "author": [ { + "name": "HAPI FHIR" + } ], + "caseSensitive": true, + "content": "complete", + "concept": [ { + "code": "SUCCESSFUL_CREATE", + "display": "Create succeeded." + }, { + "code": "SUCCESSFUL_CREATE_NO_CONDITIONAL_MATCH", + "display": "Conditional create succeeded: no existing resource matched the conditional URL." + }, { + "code": "SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH", + "display": "Conditional create succeeded: an existing resource matched the conditional URL so no action was taken." + }, { + "code": "SUCCESSFUL_UPDATE", + "display": "Update succeeded." + }, { + "code": "SUCCESSFUL_UPDATE_AS_CREATE", + "display": "Update as create succeeded." + }, { + "code": "SUCCESSFUL_UPDATE_NO_CHANGE", + "display": "Update succeeded: No changes were detected so no action was taken." + }, { + "code": "SUCCESSFUL_UPDATE_NO_CONDITIONAL_MATCH", + "display": "Conditional update succeeded: no existing resource matched the conditional URL so a new resource was created." + }, { + "code": "SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH", + "display": "Conditional update succeeded: an existing resource matched the conditional URL and was updated." + }, { + "code": "SUCCESSFUL_UPDATE_WITH_CONDITIONAL_MATCH_NO_CHANGE", + "display": "Conditional update succeeded: an existing resource matched the conditional URL and was updated, but no changes were detected so no action was taken." + }, { + "code": "SUCCESSFUL_DELETE", + "display": "Delete succeeded." + }, { + "code": "SUCCESSFUL_DELETE_ALREADY_DELETED", + "display": "Delete succeeded: Resource was already deleted so no action was taken." + }, { + "code": "SUCCESSFUL_DELETE_NOT_FOUND", + "display": "Delete succeeded: No existing resource was found so no action was taken." + }, { + "code": "SUCCESSFUL_PATCH", + "display": "Patch succeeded." + }, { + "code": "SUCCESSFUL_PATCH_NO_CHANGE", + "display": "Patch succeeded: No changes were detected so no action was taken." + }, { + "code": "SUCCESSFUL_CONDITIONAL_PATCH", + "display": "Conditional patch succeeded." + }, { + "code": "SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE", + "display": "Conditional patch succeeded: No changes were detected so no action was taken." + } ] +} \ No newline at end of file diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/HapiFhirCodeSystemGeneratorTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/HapiFhirCodeSystemGeneratorTest.java new file mode 100644 index 00000000000..d557bd785d4 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/validation/HapiFhirCodeSystemGeneratorTest.java @@ -0,0 +1,81 @@ +package ca.uhn.fhir.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.StorageResponseCodeEnum; +import ca.uhn.fhir.parser.IParser; +import org.apache.commons.io.IOUtils; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.Enumerations; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.fail; + +public class HapiFhirCodeSystemGeneratorTest { + + public static final String HAPI_FHIR_STORAGE_RESPONSE_CODE_JSON = "HapiFhirStorageResponseCode.json"; + public static final FhirContext ourCtx = FhirContext.forR5Cached(); + private static final Logger ourLog = LoggerFactory.getLogger(HapiFhirCodeSystemGeneratorTest.class); + private String myPath; + + @BeforeEach + public void findDirectory() { + String path = "hapi-fhir-validation/src/main/resources/org/hl7/fhir/common/hapi/validation/support"; + if (new File(path + "/" + HAPI_FHIR_STORAGE_RESPONSE_CODE_JSON).exists()) { + myPath = path; + return; + } else { + path = "./src/main/resources/org/hl7/fhir/common/hapi/validation/support"; + if (new File(path + "/" + HAPI_FHIR_STORAGE_RESPONSE_CODE_JSON).exists()) { + myPath = path; + return; + } + } + fail("Could not find " + HAPI_FHIR_STORAGE_RESPONSE_CODE_JSON); + } + + @Test + public void createStorageResponseCodeEnumCodeSystem() throws IOException { + CodeSystem cs = new CodeSystem(); + cs.setUrl(StorageResponseCodeEnum.SYSTEM); + cs.getAuthorFirstRep().setName("HAPI FHIR"); + cs.setStatus(Enumerations.PublicationStatus.ACTIVE); + cs.setContent(CodeSystem.CodeSystemContentMode.COMPLETE); + cs.setCaseSensitive(true); + cs.setCopyright("Licensed under the terms of the Apache Software License 2.0."); + for (var next : StorageResponseCodeEnum.values()) { + cs.addConcept() + .setCode(next.getCode()) + .setDisplay(next.getDisplay()); + } + + IParser parser = ourCtx.newJsonParser().setPrettyPrint(true); + String fileName = HAPI_FHIR_STORAGE_RESPONSE_CODE_JSON; + String encoded = parser.encodeResourceToString(cs); + + String path = myPath + "/" + fileName; + String existing; + try (FileReader r = new FileReader(path, StandardCharsets.UTF_8)) { + existing = IOUtils.toString(r); + } + + if (!existing.equals(encoded)) { + ourLog.info("Updating content of file: {}", path); + try (FileWriter w = new FileWriter(path, false)) { + w.append(encoded); + } + } else { + ourLog.info("Content of file is up to date: {}", path); + } + + } + +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java index 318c0314714..25e1781c7b6 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/FhirInstanceValidatorR4Test.java @@ -98,6 +98,7 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.lessThan; 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.assertTrue; @@ -317,6 +318,69 @@ public class FhirInstanceValidatorR4Test extends BaseTest { return retVal; } + @Test + public void testValidateStorageResponseCode() { + String input = """ + { + "resourceType": "OperationOutcome", + "text": { + "status": "generated", + "div": "

    Operation Outcome

    INFORMATION[]
    Successfully conditionally patched resource with no changes detected. Existing resource Patient/A/_history/1 matched URL: Patient?birthdate=2022-01-01. Took 6ms.
    " + }, + "issue": [ { + "severity": "information", + "code": "informational", + "details": { + "coding": [ { + "system": "https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code", + "code": "SUCCESSFUL_CONDITIONAL_PATCH_NO_CHANGE", + "display": "Conditional patch succeeded: No changes were detected so no action was taken." + } ] + }, + "diagnostics": "Successfully conditionally patched resource with no changes detected. Existing resource Patient/A/_history/1 matched URL: Patient?birthdate=2022-01-01. Took 6ms." + } ] + }"""; + FhirValidator val = ourCtx.newValidator(); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); + + ValidationResult result = val.validateWithResult(input); + List all = logResultsAndReturnErrorOnes(result); + assertTrue(result.isSuccessful(), all.toString()); + + } + + @Test + public void testValidateStorageResponseCodeBad() { + String input = """ + { + "resourceType": "OperationOutcome", + "text": { + "status": "generated", + "div": "

    Operation Outcome

    INFORMATION[]
    Successfully conditionally patched resource with no changes detected. Existing resource Patient/A/_history/1 matched URL: Patient?birthdate=2022-01-01. Took 6ms.
    " + }, + "issue": [ { + "severity": "information", + "code": "informational", + "details": { + "coding": [ { + "system": "https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code", + "code": "foo", + "display": "Conditional patch succeeded: No changes were detected so no action was taken." + } ] + }, + "diagnostics": "Successfully conditionally patched resource with no changes detected. Existing resource Patient/A/_history/1 matched URL: Patient?birthdate=2022-01-01. Took 6ms." + } ] + }"""; + FhirValidator val = ourCtx.newValidator(); + val.registerValidatorModule(new FhirInstanceValidator(myValidationSupport)); + + ValidationResult result = val.validateWithResult(input); + List all = logResultsAndReturnErrorOnes(result); + assertFalse(result.isSuccessful(), all.toString()); + assertThat(result.getMessages().get(0).getMessage(), startsWith("Unknown code 'https://hapifhir.io/fhir/CodeSystem/hapi-fhir-storage-response-code#foo'")); + + } + @Test public void testValidateCodeWithTailingSpace() { Patient p = new Patient(); diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index e331aa2d3fe..a365bf600ca 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 73ecffb1586..38e69b52f4a 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index a12a0aca3b7..7205dae7616 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. https://hapifhir.io @@ -2102,7 +2102,7 @@ ca.uhn.hapi.fhir hapi-fhir-checkstyle - 6.3.1-SNAPSHOT + 6.3.2-SNAPSHOT
    @@ -2238,7 +2238,7 @@ org.codehaus.mojo buildnumber-maven-plugin - 1.4 + 3.0.0 org.codehaus.mojo diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 94e569068be..f71a785ac41 100644 --- a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml +++ b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 6.3.1-SNAPSHOT + 6.3.2-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 aa75fd9d8a8..911b7af6db1 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.3.1-SNAPSHOT + 6.3.2-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 d6cfcd2b940..04bfb203c6b 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.3.1-SNAPSHOT + 6.3.2-SNAPSHOT ../../pom.xml