From 95d7ba6fa8b7184b62190ac5b932aaeb20337e51 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 28 Apr 2021 10:29:09 -0400 Subject: [PATCH] Streamline Search SQLs (#2576) * Streamline SQL queries * SQL query tidying * Add changelog * Test fixes * Tests passing * Ongoing work * Test fixes * Test fixes * Build fix * Cleanup * Fixes --- hapi-deployable-pom/pom.xml | 2 +- hapi-fhir-android/pom.xml | 2 +- hapi-fhir-base/pom.xml | 2 +- .../ca/uhn/fhir/rest/api/MethodOutcome.java | 5 + .../ca/uhn/fhir/i18n/hapi-messages.properties | 2 +- hapi-fhir-bom/pom.xml | 4 +- 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 | 8 +- .../2576-add-skip-upsert-existence-check.yaml | 5 + .../5_4_0/2576-new-forced-id-cache.yaml | 5 + .../5_4_0/2576-streamline-search-sql.yaml | 5 + .../hapi/fhir/docs/server_jpa/performance.md | 14 ++ hapi-fhir-jacoco/pom.xml | 2 +- hapi-fhir-jaxrsserver-base/pom.xml | 2 +- hapi-fhir-jaxrsserver-example/pom.xml | 2 +- hapi-fhir-jpaserver-api/pom.xml | 2 +- .../ca/uhn/fhir/jpa/api/config/DaoConfig.java | 76 ++++--- .../fhir/jpa/api/model/DaoMethodOutcome.java | 12 + .../jpa/api/model/LazyDaoMethodOutcome.java | 123 ++++++++++ hapi-fhir-jpaserver-base/pom.xml | 36 +-- .../bulk/export/job/GroupBulkItemReader.java | 2 +- .../ca/uhn/fhir/jpa/config/BaseConfig.java | 1 - .../ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java | 2 + .../fhir/jpa/dao/BaseHapiFhirResourceDao.java | 73 +++++- .../ca/uhn/fhir/jpa/dao/BaseStorageDao.java | 45 +++- .../jpa/dao/BaseTransactionProcessor.java | 74 ++++-- .../fhir/jpa/dao/MatchResourceUrlService.java | 30 ++- .../fhir/jpa/dao/data/IResourceTableDao.java | 3 + .../dao/expunge/ResourceExpungeService.java | 2 - .../fhir/jpa/dao/index/IdHelperService.java | 42 +++- .../jpa/search/builder/SearchBuilder.java | 4 + .../builder/sql/SearchQueryBuilder.java | 29 ++- .../uhn/fhir/jpa/util/MemoryCacheService.java | 30 ++- ...irResourceDaoCreatePlaceholdersR4Test.java | 2 +- .../r4/FhirResourceDaoR4QueryCountTest.java | 214 +++++++++++++++++- ...ourceDaoR4SearchCustomSearchParamTest.java | 7 +- .../r4/FhirResourceDaoR4SearchNoFtTest.java | 10 +- .../FhirResourceDaoR4SearchOptimizedTest.java | 2 +- .../r4/FhirResourceDaoR4SearchSqlTest.java | 47 ++++ .../dao/r4/FhirResourceDaoR4UpdateTest.java | 42 +++- ...irResourceDaoR4VersionedReferenceTest.java | 5 + .../fhir/jpa/dao/r4/FhirSystemDaoR4Test.java | 72 +++++- .../jpa/dao/r4/PartitioningSqlR4Test.java | 49 +--- .../r4/ResourceProviderHasParamR4Test.java | 1 + ...rCapabilityStatementProviderJpaR4Test.java | 4 +- hapi-fhir-jpaserver-batch/pom.xml | 2 +- hapi-fhir-jpaserver-cql/pom.xml | 6 +- hapi-fhir-jpaserver-mdm/pom.xml | 6 +- hapi-fhir-jpaserver-migrate/pom.xml | 2 +- hapi-fhir-jpaserver-model/pom.xml | 8 +- .../fhir/jpa/model/entity/ModelConfig.java | 3 + .../uhn/fhir/jpa/model/util/JpaConstants.java | 2 + hapi-fhir-jpaserver-searchparam/pom.xml | 2 +- .../fhir/jpa/searchparam/MatchUrlService.java | 5 +- hapi-fhir-jpaserver-subscription/pom.xml | 2 +- hapi-fhir-jpaserver-test-utilities/pom.xml | 2 +- hapi-fhir-jpaserver-uhnfhirtest/pom.xml | 4 +- hapi-fhir-server-mdm/pom.xml | 2 +- hapi-fhir-server-openapi/pom.xml | 2 +- hapi-fhir-server/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-structures-dstu2.1/pom.xml | 2 +- hapi-fhir-structures-dstu2/pom.xml | 2 +- hapi-fhir-structures-dstu3/pom.xml | 2 +- hapi-fhir-structures-hl7org-dstu2/pom.xml | 2 +- hapi-fhir-structures-r4/pom.xml | 2 +- hapi-fhir-structures-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 | 16 +- hapi-tinder-test/pom.xml | 2 +- pom.xml | 2 +- restful-server-example/pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- .../pom.xml | 2 +- 95 files changed, 994 insertions(+), 243 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-add-skip-upsert-existence-check.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-new-forced-id-cache.yaml create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-streamline-search-sql.yaml create mode 100644 hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/LazyDaoMethodOutcome.java create mode 100644 hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchSqlTest.java diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml index e5c236da510..b79c526e5f9 100644 --- a/hapi-deployable-pom/pom.xml +++ b/hapi-deployable-pom/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml index 29daf3ce599..e7f963fa4ac 100644 --- a/hapi-fhir-android/pom.xml +++ b/hapi-fhir-android/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml index d095fa9a09c..3f975a06dc0 100644 --- a/hapi-fhir-base/pom.xml +++ b/hapi-fhir-base/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java index 4815b1955bb..45592bb9f97 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java @@ -233,4 +233,9 @@ public class MethodOutcome { setCreated(true); } } + + protected boolean hasResource() { + return myResource != null; + } + } 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 e1e02ff0e12..28f28049461 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 @@ -72,7 +72,7 @@ ca.uhn.fhir.jpa.bulk.export.svc.BulkDataExportSvcImpl.onlyBinarySelected=Binary ca.uhn.fhir.jpa.bulk.export.svc.BulkDataExportSvcImpl.unknownResourceType=Unknown or unsupported resource type: {0} ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceVersionConstraintFailure=The operation has failed with a version constraint failure. This generally means that two clients/threads were trying to update the same resource at the same time, and this request was chosen as the failing request. ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.resourceIndexedCompositeStringUniqueConstraintFailure=The operation has failed with a unique index constraint failure. This probably means that the operation was trying to create/update a resource that would have resulted in a duplicate value for a unique index. -ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.forcedIdConstraintFailure=The operation has failed with a client-assigned ID constraint failure. This typically means that multiple client threads are trying to create a new resource with the same client-assigned ID at the same time, and this thread was chosen to be rejected. +ca.uhn.fhir.jpa.config.HapiFhirHibernateJpaDialect.forcedIdConstraintFailure=The operation has failed with a client-assigned ID constraint failure. This typically means that multiple client threads are trying to create a new resource with the same client-assigned ID at the same time, and this thread was chosen to be rejected. It can also happen when a request disables the Upsert Existence Check. ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.externalizedBinaryStorageExtensionFoundInRequestBody=Illegal extension found in request payload - URL "{0}" and value "{1}" ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.incomingNoopInTransaction=Transaction contains resource with operation NOOP. This is only valid as a response operation, not in a request diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml index e069462596a..01da0b453f9 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT pom HAPI FHIR BOM ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/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 175508dea4a..cc83f068871 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 ee4cd6101ed..91bccaf7a77 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 6735fe66129..26f1accae7f 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../../hapi-deployable-pom diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml index 377ac7c1d20..1e07c890444 100644 --- a/hapi-fhir-cli/pom.xml +++ b/hapi-fhir-cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml index 4656c910de6..a280f962927 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml index d86715e8679..5ef2183dcc1 100644 --- a/hapi-fhir-client/pom.xml +++ b/hapi-fhir-client/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml index 7778e520ac6..7f3cba8fd56 100644 --- a/hapi-fhir-converter/pom.xml +++ b/hapi-fhir-converter/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml index 95e10af58f2..717e34be8b7 100644 --- a/hapi-fhir-dist/pom.xml +++ b/hapi-fhir-dist/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml index d15eda3ec2e..7ce6ad60638 100644 --- a/hapi-fhir-docs/pom.xml +++ b/hapi-fhir-docs/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -83,13 +83,13 @@ ca.uhn.hapi.fhir hapi-fhir-structures-dstu2 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT compile ca.uhn.hapi.fhir hapi-fhir-jpaserver-subscription - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT compile @@ -106,7 +106,7 @@ ca.uhn.hapi.fhir hapi-fhir-testpage-overlay - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT classes diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-add-skip-upsert-existence-check.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-add-skip-upsert-existence-check.yaml new file mode 100644 index 00000000000..020771b98ab --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-add-skip-upsert-existence-check.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 2576 +title: "A new header called `X-Upsert-Extistence-Check` can now be added to JPA server *Create with Client-Assigned ID* operations + (aka Upsert) in order to improve performance when loading data that is known to not exist by skipping the resource existence check." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-new-forced-id-cache.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-new-forced-id-cache.yaml new file mode 100644 index 00000000000..cf9eb046310 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-new-forced-id-cache.yaml @@ -0,0 +1,5 @@ +--- +type: perf +issue: 2576 +title: "A new PID-to-forced-ID cache, and a new optional Match-URL-to-PID cache have been added. These + can improve write performance when doing large loads." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-streamline-search-sql.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-streamline-search-sql.yaml new file mode 100644 index 00000000000..44d461f4bbd --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_4_0/2576-streamline-search-sql.yaml @@ -0,0 +1,5 @@ +--- +type: perf +issue: 2576 +title: "The generated search SQL statements have been optimized for simple JPA server searches containing + only one parameter. In this case, an unnecessary JOIN has been removed." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md index 26f88208dd8..9a238f9f29a 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa/performance.md @@ -33,3 +33,17 @@ It can also be disabled at a more granular level (or selectively re-enabled if i "expression": "Observation.code" } ``` + +# Disable Upsert Existence Check + +If you are using an *Update with Client Assigned ID* (aka an Upsert), the server will perform a SQL Select in order to determine whether the ID already exists, and then proceed to create a new record if no data matches the existing row. + +If you are sure that the row does not already exist, you can add the following header to your request in order to avoid this check. + +```http +X-Upsert-Extistence-Check: disabled +``` + +This should improve write performance, so this header can be useful when large amounts of data will be created using client assigned IDs in a controlled fashion. + +If this setting is used and a resource already exists with a given client-assigned ID, a database constraint error will prevent any duplicate records from being created, and the operation will fail. diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml index 6af26480e26..d670f6beb82 100644 --- a/hapi-fhir-jacoco/pom.xml +++ b/hapi-fhir-jacoco/pom.xml @@ -11,7 +11,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 697e4479704..66d8d707b15 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index 6626a7653dc..0b78ee31001 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-jpaserver-api/pom.xml b/hapi-fhir-jpaserver-api/pom.xml index 35aecfe7055..f071882dc59 100644 --- a/hapi-fhir-jpaserver-api/pom.xml +++ b/hapi-fhir-jpaserver-api/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java index 6353d0a55b9..5dbb7bbe3c6 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java @@ -48,16 +48,16 @@ public class DaoConfig { * Default value for {@link #setReuseCachedSearchResultsForMillis(Long)}: 60000ms (one minute) */ public static final Long DEFAULT_REUSE_CACHED_SEARCH_RESULTS_FOR_MILLIS = DateUtils.MILLIS_PER_MINUTE; + /** + * See {@link #setStatusBasedReindexingDisabled(boolean)} + */ + public static final String DISABLE_STATUS_BASED_REINDEX = "disable_status_based_reindex"; /** * Default value for {@link #setTranslationCachesExpireAfterWriteInMinutes(Long)}: 60 minutes * * @see #setTranslationCachesExpireAfterWriteInMinutes(Long) */ public static final Long DEFAULT_TRANSLATION_CACHES_EXPIRE_AFTER_WRITE_IN_MINUTES = 60L; - /** - * See {@link #setStatusBasedReindexingDisabled(boolean)} - */ - public static final String DISABLE_STATUS_BASED_REINDEX = "disable_status_based_reindex"; /** * Default {@link #setBundleTypesAllowedForStorage(Set)} value: *
    @@ -91,11 +91,6 @@ public class DaoConfig { private final ModelConfig myModelConfig = new ModelConfig(); - /** - * update setter javadoc if default changes - */ - @Nonnull - private Long myTranslationCachesExpireAfterWriteInMinutes = DEFAULT_TRANSLATION_CACHES_EXPIRE_AFTER_WRITE_IN_MINUTES; /** * update setter javadoc if default changes */ @@ -201,6 +196,15 @@ public class DaoConfig { * @since 5.2.0 */ private boolean myUseLegacySearchBuilder = false; + /** + * update setter javadoc if default changes + */ + @Nonnull + private Long myTranslationCachesExpireAfterWriteInMinutes = DEFAULT_TRANSLATION_CACHES_EXPIRE_AFTER_WRITE_IN_MINUTES; + /** + * @since 5.4.0 + */ + private boolean myMatchUrlCache; /** * Constructor @@ -253,6 +257,43 @@ public class DaoConfig { return myUseLegacySearchBuilder; } + /** + * Specifies the duration in minutes for which values will be retained after being + * written to the terminology translation cache. Defaults to 60. + */ + @Nonnull + public Long getTranslationCachesExpireAfterWriteInMinutes() { + return myTranslationCachesExpireAfterWriteInMinutes; + } + + /** + * If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be + * cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers + * where conditional operations are frequently performed, but note that this cache will not be + * invalidated based on updates to resources so this may have detrimental effects. + * + * Default is false + * + * @since 5.4.0 + */ + public void setMatchUrlCache(boolean theMatchUrlCache) { + myMatchUrlCache = theMatchUrlCache; + } + + /** + * If enabled, resolutions for match URLs (e.g. conditional create URLs, conditional update URLs, etc) will be + * cached in an in-memory cache. This cache can have a noticeable improvement on write performance on servers + * where conditional operations are frequently performed, but note that this cache will not be + * invalidated based on updates to resources so this may have detrimental effects. + * + * Default is false + * + * @since 5.4.0 + */ + public boolean getMatchUrlCache() { + return myMatchUrlCache; + } + /** * This method controls whether to use the new non-hibernate search SQL builder that was introduced in HAPI FHIR 5.2.0. * By default this will be false meaning that the new SQL builder is used. Set to true to use the @@ -882,23 +923,6 @@ public class DaoConfig { myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis; } - /** - * Specifies the duration in minutes for which values will be retained after being - * written to the terminology translation cache. Defaults to 60. - */ - @Nonnull - public Long getTranslationCachesExpireAfterWriteInMinutes() { - return myTranslationCachesExpireAfterWriteInMinutes; - } - - /** - * Specifies the duration in minutes for which values will be retained after being - * written to the terminology translation cache. Defaults to 60. - */ - public void setTranslationCachesExpireAfterWriteInMinutes(Long translationCachesExpireAfterWriteInMinutes) { - myTranslationCachesExpireAfterWriteInMinutes = translationCachesExpireAfterWriteInMinutes; - } - /** * This setting may be used to advise the server that any references found in * resources that have any of the base URLs given here will be treated as logical diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java index ff0d47d577e..9c954089dd3 100644 --- a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/DaoMethodOutcome.java +++ b/hapi-fhir-jpaserver-api/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.server.storage.ResourcePersistentId; import org.hl7.fhir.instance.model.api.IBaseResource; public class DaoMethodOutcome extends MethodOutcome { @@ -29,6 +30,7 @@ public class DaoMethodOutcome extends MethodOutcome { private IBasePersistedResource myEntity; private IBaseResource myPreviousResource; private boolean myNop; + private ResourcePersistentId myResourcePersistentId; /** * Constructor @@ -82,4 +84,14 @@ public class DaoMethodOutcome extends MethodOutcome { super.setCreated(theCreated); return this; } + + public DaoMethodOutcome setPersistentId(ResourcePersistentId theResourcePersistentId) { + myResourcePersistentId = theResourcePersistentId; + return this; + } + + public ResourcePersistentId getPersistentId() { + return myResourcePersistentId; + } + } diff --git a/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/LazyDaoMethodOutcome.java b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/LazyDaoMethodOutcome.java new file mode 100644 index 00000000000..0ed9808a84e --- /dev/null +++ b/hapi-fhir-jpaserver-api/src/main/java/ca/uhn/fhir/jpa/api/model/LazyDaoMethodOutcome.java @@ -0,0 +1,123 @@ +package ca.uhn.fhir.jpa.api.model; + +/* + * #%L + * HAPI FHIR JPA API + * %% + * Copyright (C) 2014 - 2021 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 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.function.Supplier; + +public class LazyDaoMethodOutcome extends DaoMethodOutcome { + + private Supplier myEntitySupplier; + private Supplier myIdSupplier; + private Runnable myEntitySupplierUseCallback; + + /** + * Constructor + */ + public LazyDaoMethodOutcome(ResourcePersistentId theResourcePersistentId) { + setPersistentId(theResourcePersistentId); + } + + @Override + public IBasePersistedResource getEntity() { + IBasePersistedResource retVal = super.getEntity(); + if (retVal == null) { + tryToRunSupplier(); + retVal = super.getEntity(); + } + return retVal; + } + + private void tryToRunSupplier() { + if (myEntitySupplier != null) { + + EntityAndResource entityAndResource = myEntitySupplier.get(); + setEntity(entityAndResource.getEntity()); + setResource(entityAndResource.getResource()); + setId(entityAndResource.getResource().getIdElement()); + myEntitySupplierUseCallback.run(); + + } + } + + @Override + public IIdType getId() { + IIdType retVal = super.getId(); + if (retVal == null) { + if (super.hasResource()) { + retVal = getResource().getIdElement(); + setId(retVal); + } else { + if (myIdSupplier != null) { + retVal = myIdSupplier.get(); + setId(retVal); + } + } + } + return retVal; + } + + @Override + public IBaseResource getResource() { + IBaseResource retVal = super.getResource(); + if (retVal == null) { + tryToRunSupplier(); + retVal = super.getResource(); + } + return retVal; + } + + public void setEntitySupplier(Supplier theEntitySupplier) { + myEntitySupplier = theEntitySupplier; + } + + public void setEntitySupplierUseCallback(Runnable theEntitySupplierUseCallback) { + myEntitySupplierUseCallback = theEntitySupplierUseCallback; + } + + public void setIdSupplier(Supplier theIdSupplier) { + myIdSupplier = theIdSupplier; + } + + + public static class EntityAndResource { + private final IBasePersistedResource myEntity; + private final IBaseResource myResource; + + public EntityAndResource(IBasePersistedResource theEntity, IBaseResource theResource) { + myEntity = theEntity; + myResource = theResource; + } + + public IBasePersistedResource getEntity() { + return myEntity; + } + + public IBaseResource getResource() { + return myResource; + } + } + +} diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index 357583621aa..c200b393be0 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -577,14 +577,14 @@ test - - ca.uhn.hapi.fhir - hapi-fhir-server-openapi - ${project.version} - test - + + ca.uhn.hapi.fhir + hapi-fhir-server-openapi + ${project.version} + test + - + com.github.ben-manes.caffeine caffeine @@ -604,13 +604,19 @@ org.jetbrains annotations - - org.mortbay.jetty - servlet-api - 2.5-20081211 - test - - + + org.mortbay.jetty + servlet-api + 2.5-20081211 + test + + + org.apache.jena + jena-arq + 3.17.0 + compile + + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/GroupBulkItemReader.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/GroupBulkItemReader.java index 6b49a2134b7..f857e07963e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/GroupBulkItemReader.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/bulk/export/job/GroupBulkItemReader.java @@ -160,7 +160,7 @@ public class GroupBulkItemReader extends BaseBulkItemReader implements ItemReade // } Map sourceResourceIdToGoldenResourceIdMap = new HashMap<>(); goldenResourceToSourcePidMap.forEach((key, value) -> { - String goldenResourceId = myIdHelperService.translatePidIdToForcedId(new ResourcePersistentId(key)).orElse(key.toString()); + String goldenResourceId = myIdHelperService.translatePidIdToForcedIdWithCache(new ResourcePersistentId(key)).orElse(key.toString()); Map> pidsToForcedIds = myIdHelperService.translatePidsToForcedIds(value); Set sourceResourceIds = pidsToForcedIds.entrySet().stream() diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index e2a5f608117..22f30a960fd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -164,7 +164,6 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import java.util.Date; -import java.util.concurrent.RejectedExecutionHandler; /* * #%L 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 005320e8e67..1290298f4bb 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 @@ -280,6 +280,8 @@ public abstract class BaseHapiFhirDao extends BaseStora retVal.setPartitionId(theEntity.getPartitionId()); theEntity.setForcedId(retVal); } + } else if (theEntity.getForcedId() != null) { + retVal = theEntity.getForcedId(); } return retVal; 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 cf768c557d7..d0f1c109ee9 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 @@ -33,6 +33,7 @@ import ca.uhn.fhir.jpa.api.model.DeleteConflictList; import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome; import ca.uhn.fhir.jpa.api.model.ExpungeOptions; import ca.uhn.fhir.jpa.api.model.ExpungeOutcome; +import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome; import ca.uhn.fhir.jpa.dao.expunge.DeleteExpungeService; import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService; import ca.uhn.fhir.jpa.delete.DeleteConflictService; @@ -54,19 +55,20 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider; import ca.uhn.fhir.jpa.search.cache.SearchCacheStatusEnum; import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; -import ca.uhn.fhir.rest.api.InterceptorInvocationTimingEnum; -import ca.uhn.fhir.rest.api.SearchContainedModeEnum; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.dstu2.resource.ListResource; import ca.uhn.fhir.model.primitive.IdDt; 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; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.IPreResourceAccessDetails; @@ -135,6 +137,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.defaultString; @@ -173,6 +176,8 @@ public abstract class BaseHapiFhirResourceDao extends B private Class myResourceType; @Autowired private IRequestPartitionHelperSvc myPartitionHelperSvc; + @Autowired + private MemoryCacheService myMemoryCacheService; @Override @Transactional @@ -289,10 +294,26 @@ public abstract class BaseHapiFhirResourceDao extends B throw new PreconditionFailedException(msg); } else if (match.size() == 1) { ResourcePersistentId pid = match.iterator().next(); - entity = myEntityManager.find(ResourceTable.class, pid.getId()); - IBaseResource resource = toResource(entity, false); - theResource.setId(resource.getIdElement().getValue()); - return toMethodOutcome(theRequest, entity, resource).setCreated(false).setNop(true); + + Supplier entitySupplier = () -> { + ResourceTable foundEntity = myEntityManager.find(ResourceTable.class, pid.getId()); + IBaseResource resource = toResource(foundEntity, false); + theResource.setId(resource.getIdElement().getValue()); + return new LazyDaoMethodOutcome.EntityAndResource(foundEntity, resource); + }; + + Supplier idSupplier = () -> { + IIdType retVal = myIdHelperService.translatePidIdToForcedId(myFhirContext, myResourceName, pid); + if (!retVal.hasVersionIdPart()) { + return myMemoryCacheService.get(MemoryCacheService.CacheEnum.RESOURCE_CONDITIONAL_CREATE_VERSION, retVal, t -> { + long version = myResourceTableDao.findCurrentVersionByPid(pid.getIdAsLong()); + return myFhirContext.getVersion().newIdType().setParts(retVal.getBaseUrl(), retVal.getResourceType(), retVal.getIdPart(), Long.toString(version)); + }); + } + return retVal; + }; + + return toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true); } } @@ -355,6 +376,10 @@ public abstract class BaseHapiFhirResourceDao extends B } } + if (theIfNoneExist != null) { + myMatchResourceUrlService.matchUrlResolved(theIfNoneExist, new ResourcePersistentId(entity.getResourceId())); + } + /* * If we aren't indexing (meaning we're probably executing a sub-operation within a transaction), * we'll manually increase the version. This is important because we want the updated version number @@ -390,6 +415,14 @@ public abstract class BaseHapiFhirResourceDao extends B String msg = getContext().getLocalizer().getMessageSanitized(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); outcome.setOperationOutcome(createInfoOperationOutcome(msg)); + String forcedId = null; + if (updatedEntity.getForcedId() != null) { + forcedId = updatedEntity.getForcedId().getForcedId(); + } + if (myIdHelperService != null) { + myIdHelperService.addResolvedPidToForcedId(new ResourcePersistentId(updatedEntity.getResourceId()), theRequestPartitionId, getResourceName(), forcedId); + } + ourLog.debug(msg); return outcome; } @@ -437,7 +470,7 @@ public abstract class BaseHapiFhirResourceDao extends B // Don't delete again if it's already deleted if (entity.getDeleted() != null) { - DaoMethodOutcome outcome = new DaoMethodOutcome(); + DaoMethodOutcome outcome = new DaoMethodOutcome().setPersistentId(new ResourcePersistentId(entity.getResourceId())); outcome.setEntity(entity); IIdType id = getContext().getVersion().newIdType(); @@ -1546,7 +1579,7 @@ public abstract class BaseHapiFhirResourceDao extends B preProcessResourceForStorage(resource); preProcessResourceForStorage(theResource, theRequest, theTransactionDetails, thePerformIndexing); - final ResourceTable entity; + ResourceTable entity = null; IIdType resourceId; if (isNotBlank(theMatchUrl)) { @@ -1572,9 +1605,25 @@ public abstract class BaseHapiFhirResourceDao extends B assert resourceId.hasIdPart(); RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineReadPartitionForRequest(theRequest, getResourceName()); - try { - entity = readEntityLatestVersion(resourceId, requestPartitionId); - } catch (ResourceNotFoundException e) { + + boolean create = false; + + if (theRequest != null) { + String existenceCheck = theRequest.getHeader(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK); + if (JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK_DISABLED.equals(existenceCheck)) { + create = true; + } + } + + if (!create) { + try { + entity = readEntityLatestVersion(resourceId, requestPartitionId); + } catch (ResourceNotFoundException e) { + create = true; + } + } + + if (create) { requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequest, theResource, getResourceName()); return doCreateForPostOrPut(resource, null, thePerformIndexing, theTransactionDetails, theRequest, requestPartitionId); } @@ -1616,6 +1665,8 @@ public abstract class BaseHapiFhirResourceDao extends B 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; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java index 0dc80bf9500..01a3fe9edc0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseStorageDao.java @@ -29,11 +29,13 @@ 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.model.DaoMethodOutcome; +import ca.uhn.fhir.jpa.api.model.LazyDaoMethodOutcome; import ca.uhn.fhir.jpa.model.cross.IBasePersistedResource; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; import ca.uhn.fhir.model.api.IQueryParameterAnd; @@ -73,6 +75,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_ERROR; import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.OO_SEVERITY_INFO; @@ -212,7 +215,7 @@ public abstract class BaseStorageDao { } protected DaoMethodOutcome toMethodOutcome(RequestDetails theRequest, @Nonnull final IBasePersistedResource theEntity, @Nonnull IBaseResource theResource) { - DaoMethodOutcome outcome = new DaoMethodOutcome(); + DaoMethodOutcome outcome = new DaoMethodOutcome().setPersistentId(theEntity.getPersistentId()); if (theEntity instanceof ResourceTable) { if (((ResourceTable) theEntity).isUnchangedInCurrentOperation()) { @@ -269,6 +272,46 @@ public abstract class BaseStorageDao { return outcome; } + protected DaoMethodOutcome toMethodOutcomeLazy(RequestDetails theRequest, ResourcePersistentId theResourcePersistentId, @Nonnull final Supplier theEntity, Supplier theIdSupplier) { + LazyDaoMethodOutcome outcome = new LazyDaoMethodOutcome(theResourcePersistentId); + + outcome.setEntitySupplier(theEntity); + outcome.setIdSupplier(theIdSupplier); + outcome.setEntitySupplierUseCallback(()->{ + // Interceptor broadcast: STORAGE_PREACCESS_RESOURCES + if (outcome.getResource() != null) { + SimplePreResourceAccessDetails accessDetails = new SimplePreResourceAccessDetails(outcome.getResource()); + HookParams params = new HookParams() + .add(IPreResourceAccessDetails.class, accessDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequest, Pointcut.STORAGE_PREACCESS_RESOURCES, params); + if (accessDetails.isDontReturnResourceAtIndex(0)) { + outcome.setResource(null); + } + } + + // Interceptor broadcast: STORAGE_PRESHOW_RESOURCES + // Note that this will only fire if someone actually goes to use the + // resource in a response (it's their responsibility to call + // outcome.fireResourceViewCallback()) + outcome.registerResourceViewCallback(() -> { + if (outcome.getResource() != null) { + SimplePreResourceShowDetails showDetails = new SimplePreResourceShowDetails(outcome.getResource()); + HookParams params = new HookParams() + .add(IPreResourceShowDetails.class, showDetails) + .add(RequestDetails.class, theRequest) + .addIfMatchesType(ServletRequestDetails.class, theRequest); + JpaInterceptorBroadcaster.doCallHooks(getInterceptorBroadcaster(), theRequest, Pointcut.STORAGE_PRESHOW_RESOURCES, params); + outcome.setResource(showDetails.getResource(0)); + } + }); + }); + + return outcome; + } + + protected void doCallHooks(TransactionDetails theTransactionDetails, RequestDetails theRequestDetails, Pointcut thePointcut, HookParams theParams) { if (theTransactionDetails.isAcceptingDeferredInterceptorBroadcasts(thePointcut)) { theTransactionDetails.addDeferredInterceptorBroadcast(thePointcut, theParams); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java index 859b6f90c65..7b75a99983e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseTransactionProcessor.java @@ -226,11 +226,11 @@ public abstract class BaseTransactionProcessor { myVersionAdapter.setResponseLastModified(newEntry, lastModifier); if (theRequestDetails != null) { - if (outcome.getResource() != null) { String prefer = theRequestDetails.getHeader(Constants.HEADER_PREFER); PreferReturnEnum preferReturn = RestfulServerUtils.parsePreferHeader(null, prefer).getReturn(); if (preferReturn != null) { if (preferReturn == PreferReturnEnum.REPRESENTATION) { + if (outcome.getResource() != null) { outcome.fireResourceViewCallbacks(); myVersionAdapter.setResource(newEntry, outcome.getResource()); } @@ -440,21 +440,21 @@ public abstract class BaseTransactionProcessor { * heavy load with lots of concurrent transactions using all available * database connections. */ - TransactionCallback> txCallback = status -> { + TransactionCallback> txCallback = status -> { final Set allIds = new LinkedHashSet<>(); final Map idSubstitutions = new HashMap<>(); final Map idToPersistedOutcome = new HashMap<>(); - Map retVal = doTransactionWriteOperations(theRequestDetails, theActionName, transactionDetails, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch); + Map retVal = doTransactionWriteOperations(theRequestDetails, theActionName, transactionDetails, allIds, idSubstitutions, idToPersistedOutcome, response, originalRequestOrder, entries, transactionStopWatch); transactionStopWatch.startTask("Commit writes to database"); return retVal; }; - Map entriesToProcess = myHapiTransactionService.execute(theRequestDetails, txCallback); + Map entriesToProcess = myHapiTransactionService.execute(theRequestDetails, txCallback); transactionStopWatch.endCurrentTask(); - for (Map.Entry nextEntry : entriesToProcess.entrySet()) { - String responseLocation = nextEntry.getValue().getIdDt().toUnqualified().getValue(); - String responseEtag = nextEntry.getValue().getIdDt().getVersionIdPart(); + for (Map.Entry nextEntry : entriesToProcess.entrySet()) { + String responseLocation = nextEntry.getValue().toUnqualified().getValue(); + String responseEtag = nextEntry.getValue().getVersionIdPart(); myVersionAdapter.setResponseLocation(nextEntry.getKey(), responseLocation); myVersionAdapter.setResponseETag(nextEntry.getKey(), responseEtag); } @@ -576,7 +576,7 @@ public abstract class BaseTransactionProcessor { myModelConfig = theModelConfig; } - private Map doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set theAllIds, + private Map doTransactionWriteOperations(final RequestDetails theRequest, String theActionName, TransactionDetails theTransactionDetails, Set theAllIds, Map theIdSubstitutions, Map theIdToPersistedOutcome, IBaseBundle theResponse, IdentityHashMap theOriginalRequestOrder, List theEntries, StopWatch theTransactionStopWatch) { theTransactionDetails.beginAcceptingDeferredInterceptorBroadcasts( @@ -588,8 +588,8 @@ public abstract class BaseTransactionProcessor { Set deletedResources = new HashSet<>(); DeleteConflictList deleteConflicts = new DeleteConflictList(); - Map entriesToProcess = new IdentityHashMap<>(); - Set nonUpdatedEntities = new HashSet<>(); + Map entriesToProcess = new IdentityHashMap<>(); + Set nonUpdatedEntities = new HashSet<>(); Set updatedEntities = new HashSet<>(); List updatedResources = new ArrayList<>(); Map> conditionalRequestUrls = new HashMap<>(); @@ -726,9 +726,9 @@ public abstract class BaseTransactionProcessor { if (nextResourceId != null) { handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); } - entriesToProcess.put(nextRespEntry, outcome.getEntity()); + entriesToProcess.put(nextRespEntry, outcome.getId()); if (outcome.getCreated() == false) { - nonUpdatedEntities.add(outcome.getEntity()); + nonUpdatedEntities.add(outcome.getId()); } else { if (isNotBlank(matchUrl)) { conditionalRequestUrls.put(matchUrl, res.getClass()); @@ -749,7 +749,7 @@ public abstract class BaseTransactionProcessor { DaoMethodOutcome outcome = dao.delete(deleteId, deleteConflicts, theRequest, theTransactionDetails); if (outcome.getEntity() != null) { deletedResources.add(deleteId.getValueAsString()); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); + entriesToProcess.put(nextRespEntry, outcome.getId()); } } } else { @@ -812,7 +812,7 @@ public abstract class BaseTransactionProcessor { } handleTransactionCreateOrUpdateOutcome(theIdSubstitutions, theIdToPersistedOutcome, nextResourceId, outcome, nextRespEntry, resourceType, res, theRequest); - entriesToProcess.put(nextRespEntry, outcome.getEntity()); + entriesToProcess.put(nextRespEntry, outcome.getId()); break; } case "PATCH": { @@ -916,7 +916,7 @@ public abstract class BaseTransactionProcessor { } DeleteConflictService.validateDeleteConflictsEmptyOrThrowException(myContext, deleteConflicts); - theIdToPersistedOutcome.entrySet().forEach(t -> theTransactionDetails.addResolvedResourceId(t.getKey(), t.getValue().getEntity().getPersistentId())); + theIdToPersistedOutcome.entrySet().forEach(t -> theTransactionDetails.addResolvedResourceId(t.getKey(), t.getValue().getPersistentId())); /* * Perform ID substitutions and then index each resource we have saved @@ -931,6 +931,10 @@ public abstract class BaseTransactionProcessor { ourLog.debug("Have indexed {} entities out of {} in transaction", i, theIdToPersistedOutcome.values().size()); } + if (nextOutcome.isNop()) { + continue; + } + IBaseResource nextResource = nextOutcome.getResource(); if (nextResource == null) { continue; @@ -942,11 +946,23 @@ public abstract class BaseTransactionProcessor { for (ResourceReferenceInfo nextRef : allRefs) { IBaseReference resourceReference = nextRef.getResourceReference(); IIdType nextId = resourceReference.getReferenceElement(); + IIdType newId = null; if (!nextId.hasIdPart()) { - continue; + if (resourceReference.getResource() != null) { + IIdType targetId = resourceReference.getResource().getIdElement(); + if (theIdSubstitutions.containsValue(targetId)) { + newId = targetId; + } else { + throw new InternalErrorException("References by resource with no reference ID are not supported in DAO layer"); + } + } else { + continue; + } } - if (theIdSubstitutions.containsKey(nextId)) { - IIdType newId = theIdSubstitutions.get(nextId); + if (newId != null || theIdSubstitutions.containsKey(nextId)) { + if (newId == null) { + newId = theIdSubstitutions.get(nextId); + } ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId); if (referencesToVersion.contains(resourceReference)) { resourceReference.setReference(newId.getValue()); @@ -993,11 +1009,27 @@ public abstract class BaseTransactionProcessor { IFhirResourceDao dao = myDaoRegistry.getResourceDao(nextResource.getClass()); IJpaDao jpaDao = (IJpaDao) dao; + IBasePersistedResource updateOutcome = null; if (updatedEntities.contains(nextOutcome.getEntity())) { - jpaDao.updateInternal(theRequest, nextResource, true, false, nextOutcome.getEntity(), nextResource.getIdElement(), nextOutcome.getPreviousResource(), theTransactionDetails); - } else if (!nonUpdatedEntities.contains(nextOutcome.getEntity())) { - jpaDao.updateEntity(theRequest, nextResource, nextOutcome.getEntity(), deletedTimestampOrNull, true, false, theTransactionDetails, false, true); + updateOutcome = jpaDao.updateInternal(theRequest, nextResource, true, false, 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); } + + // Make sure we reflect the actual final version for the resource. + if (updateOutcome != null) { + IIdType newId = updateOutcome.getIdDt(); + for (IIdType nextEntry : entriesToProcess.values()) { + if (nextEntry.getResourceType().equals(newId.getResourceType())) { + if (nextEntry.getIdPart().equals(newId.getIdPart())) { + if (!nextEntry.hasVersionIdPart() || !nextEntry.getVersionIdPart().equals(newId.getVersionIdPart())) { + nextEntry.setParts(nextEntry.getBaseUrl(), nextEntry.getResourceType(), nextEntry.getIdPart(), newId.getVersionIdPart()); + } + } + } + } + } + } theTransactionStopWatch.endCurrentTask(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java index c03409f7917..5de75fc1a3e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/MatchResourceUrlService.java @@ -25,22 +25,26 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; +import ca.uhn.fhir.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.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.MatchUrlService; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster; +import ca.uhn.fhir.jpa.util.MemoryCacheService; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.StopWatch; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.Set; @Service @@ -52,9 +56,20 @@ public class MatchResourceUrlService { @Autowired private MatchUrlService myMatchUrlService; @Autowired + private DaoConfig myDaoConfig; + @Autowired private IInterceptorBroadcaster myInterceptorBroadcaster; + @Autowired + private MemoryCacheService myMemoryCacheService; public Set processMatchUrl(String theMatchUrl, Class theResourceType, RequestDetails theRequest) { + if (myDaoConfig.getMatchUrlCache()) { + ResourcePersistentId existing = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl); + if (existing != null) { + return Collections.singleton(existing); + } + } + RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(theResourceType); SearchParameterMap paramMap = myMatchUrlService.translateMatchUrl(theMatchUrl, resourceDef); if (paramMap.isEmpty() && paramMap.getLastUpdated() == null) { @@ -62,7 +77,13 @@ public class MatchResourceUrlService { } paramMap.setLoadSynchronous(true); - return search(paramMap, theResourceType, theRequest); + Set retVal = search(paramMap, theResourceType, theRequest); + + if (myDaoConfig.getMatchUrlCache() && retVal.size() == 1) { + myMemoryCacheService.put(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, retVal.iterator().next()); + } + + return retVal; } public Set search(SearchParameterMap theParamMap, Class theResourceType, RequestDetails theRequest) { @@ -88,4 +109,11 @@ public class MatchResourceUrlService { } + public void matchUrlResolved(String theMatchUrl, ResourcePersistentId theResourcePersistentId) { + Validate.notBlank(theMatchUrl); + Validate.notNull(theResourcePersistentId); + if (myDaoConfig.getMatchUrlCache()) { + myMemoryCacheService.put(MemoryCacheService.CacheEnum.MATCH_URL, theMatchUrl, theResourcePersistentId); + } + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java index 90eaaad10fa..d0fc8b90ea6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/data/IResourceTableDao.java @@ -91,4 +91,7 @@ public interface IResourceTableDao extends JpaRepository { */ @Query("SELECT t.myResourceType, t.myId, t.myDeleted FROM ResourceTable t WHERE t.myId IN (:pid) AND t.myPartitionIdValue IS NULL") Collection findLookupFieldsByResourcePidInPartitionNull(@Param("pid") List thePids); + + @Query("SELECT t.myVersion FROM ResourceTable t WHERE t.myId = :pid") + long findCurrentVersionByPid(@Param("pid") Long thePid); } 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 525cbd16b13..32a5fafd8e4 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 @@ -62,10 +62,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; import org.springframework.stereotype.Service; -import org.springframework.transaction.TransactionManager; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionSynchronization; -import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.util.Collections; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java index d28ba15b5d1..b60eb46b89f 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/IdHelperService.java @@ -136,20 +136,25 @@ public class IdHelperService { public ResourcePersistentId resolveResourcePersistentIds(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) { Validate.notNull(theId, "theId must not be null"); - Long retVal; + ResourcePersistentId retVal; if (myDaoConfig.getResourceClientIdStrategy() == DaoConfig.ClientIdStrategyEnum.ANY || !isValidPid(theId)) { if (myDaoConfig.isDeleteEnabled()) { - retVal = resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId(); + retVal = new ResourcePersistentId(resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId()); } else { - String key = RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId; - retVal = myMemoryCacheService.get(MemoryCacheService.CacheEnum.PERSISTENT_ID, key, t -> resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId()); + String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, theId); + retVal = myMemoryCacheService.get(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, t -> new ResourcePersistentId(resolveResourceIdentity(theRequestPartitionId, theResourceType, theId).getResourceId())); } } else { - retVal = Long.parseLong(theId); + retVal = new ResourcePersistentId(Long.parseLong(theId)); } - return new ResourcePersistentId(retVal); + return retVal; + } + + @Nonnull + private String toForcedIdToPidKey(@Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, String theId) { + return RequestPartitionId.stringifyForKey(theRequestPartitionId) + "/" + theResourceType + "/" + theId; } /** @@ -237,7 +242,7 @@ public class IdHelperService { public IIdType translatePidIdToForcedId(FhirContext theCtx, String theResourceType, ResourcePersistentId theId) { IIdType retVal = theCtx.getVersion().newIdType(); - Optional forcedId = translatePidIdToForcedId(theId); + Optional forcedId = translatePidIdToForcedIdWithCache(theId); if (forcedId.isPresent()) { retVal.setValue(theResourceType + '/' + forcedId.get()); } else { @@ -248,8 +253,8 @@ public class IdHelperService { } - public Optional translatePidIdToForcedId(ResourcePersistentId theId) { - return myMemoryCacheService.get(MemoryCacheService.CacheEnum.FORCED_ID, theId.getIdAsLong(), pid -> myForcedIdDao.findByResourcePid(pid).map(t -> t.getForcedId())); + public Optional translatePidIdToForcedIdWithCache(ResourcePersistentId theId) { + return myMemoryCacheService.get(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theId.getIdAsLong(), pid -> myForcedIdDao.findByResourcePid(pid).map(t -> t.getForcedId())); } private ListMultimap organizeIdsByResourceType(Collection theIds) { @@ -404,7 +409,7 @@ public class IdHelperService { } public Map> translatePidsToForcedIds(Set thePids) { - Map> retVal = new HashMap<>(myMemoryCacheService.getAllPresent(MemoryCacheService.CacheEnum.FORCED_ID, thePids)); + Map> retVal = new HashMap<>(myMemoryCacheService.getAllPresent(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, thePids)); List remainingPids = thePids .stream() @@ -418,7 +423,7 @@ public class IdHelperService { Long nextResourcePid = forcedId.getResourceId(); Optional nextForcedId = Optional.of(forcedId.getForcedId()); retVal.put(nextResourcePid, nextForcedId); - myMemoryCacheService.put(MemoryCacheService.CacheEnum.FORCED_ID, nextResourcePid, nextForcedId); + myMemoryCacheService.put(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, nextForcedId); } }); @@ -428,7 +433,7 @@ public class IdHelperService { .collect(Collectors.toList()); for (Long nextResourcePid : remainingPids) { retVal.put(nextResourcePid, Optional.empty()); - myMemoryCacheService.put(MemoryCacheService.CacheEnum.FORCED_ID, nextResourcePid, Optional.empty()); + myMemoryCacheService.put(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, nextResourcePid, Optional.empty()); } return retVal; @@ -481,6 +486,19 @@ public class IdHelperService { return optionalResource.get().getIdDt().toVersionless(); } + /** + * Pre-cache a PID-to-Resource-ID mapping for later retrieval by {@link #translatePidsToForcedIds(Set)} and related methods + */ + public void addResolvedPidToForcedId(ResourcePersistentId theResourcePersistentId, @Nonnull RequestPartitionId theRequestPartitionId, String theResourceType, @Nullable String theForcedId) { + if (theForcedId != null) { + myMemoryCacheService.put(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.of(theForcedId)); + String key = toForcedIdToPidKey(theRequestPartitionId, theResourceType, theForcedId); + myMemoryCacheService.put(MemoryCacheService.CacheEnum.FORCED_ID_TO_PID, key, theResourcePersistentId); + }else { + myMemoryCacheService.put(MemoryCacheService.CacheEnum.PID_TO_FORCED_ID, theResourcePersistentId.getIdAsLong(), Optional.empty()); + } + } + public static boolean isValidPid(IIdType theId) { if (theId == null) { return false; 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 230bdefa531..b985e17bf57 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 @@ -362,6 +362,10 @@ public class SearchBuilder implements ISearchBuilder { SearchQueryBuilder sqlBuilder = new SearchQueryBuilder(myContext, myDaoConfig.getModelConfig(), myPartitionSettings, myRequestPartitionId, sqlBuilderResourceName, mySqlBuilderFactory, myDialectProvider, theCount); QueryStack queryStack3 = new QueryStack(theParams, myDaoConfig, myDaoConfig.getModelConfig(), myContext, sqlBuilder, mySearchParamRegistry, myPartitionSettings); + if (theParams.keySet().size() > 1 || theParams.getSort() != null || theParams.keySet().contains(Constants.PARAM_HAS)) { + sqlBuilder.setNeedResourceTableRoot(true); + } + JdbcTemplate jdbcTemplate = new JdbcTemplate(myEntityManagerFactory.getDataSource()); jdbcTemplate.setFetchSize(myFetchSize); if (theMaximumResults != null) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java index d7b94c90470..06058baed6d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/sql/SearchQueryBuilder.java @@ -101,12 +101,13 @@ public class SearchQueryBuilder { private BaseJoiningPredicateBuilder myFirstPredicateBuilder; private boolean dialectIsMsSql; private boolean dialectIsMySql; + private boolean myNeedResourceTableRoot; /** * Constructor */ public SearchQueryBuilder(FhirContext theFhirContext, ModelConfig theModelConfig, PartitionSettings thePartitionSettings, RequestPartitionId theRequestPartitionId, String theResourceType, SqlObjectFactory theSqlBuilderFactory, HibernatePropertiesProvider theDialectProvider, boolean theCountQuery) { - this(theFhirContext, theModelConfig, thePartitionSettings, theRequestPartitionId, theResourceType, theSqlBuilderFactory, UUID.randomUUID().toString() + "-", theDialectProvider.getDialect(), theCountQuery, new ArrayList<>()); + this(theFhirContext, theModelConfig, thePartitionSettings, theRequestPartitionId, theResourceType, theSqlBuilderFactory, UUID.randomUUID() + "-", theDialectProvider.getDialect(), theCountQuery, new ArrayList<>()); } /** @@ -310,11 +311,16 @@ public class SearchQueryBuilder { addJoin(fromTable, toTable, theSourceJoinColumn, toColumn); } else { if (myFirstPredicateBuilder == null) { - ResourceTablePredicateBuilder root; - if (thePredicateBuilder instanceof ResourceTablePredicateBuilder) { - root = (ResourceTablePredicateBuilder) thePredicateBuilder; + + BaseJoiningPredicateBuilder root; + if (!myNeedResourceTableRoot) { + root = thePredicateBuilder; } else { - root = mySqlBuilderFactory.resourceTable(this); + if (thePredicateBuilder instanceof ResourceTablePredicateBuilder) { + root = thePredicateBuilder; + } else { + root = mySqlBuilderFactory.resourceTable(this); + } } if (myCountQuery) { @@ -325,7 +331,7 @@ public class SearchQueryBuilder { mySelect.addFromTable(root.getTable()); myFirstPredicateBuilder = root; - if (thePredicateBuilder instanceof ResourceTablePredicateBuilder) { + if (!myNeedResourceTableRoot || (thePredicateBuilder instanceof ResourceTablePredicateBuilder)) { return; } } @@ -680,4 +686,15 @@ public class SearchQueryBuilder { mySelect.addCustomOrderings(orderObject); } + /** + * If set to true (default is false), force the generated SQL to start + * with the {@link ca.uhn.fhir.jpa.model.entity.ResourceTable HFJ_RESOURCE} + * table at the root of the query. + * + * This seems to perform better if there are multiple joins on the + * resource ID table. + */ + public void setNeedResourceTableRoot(boolean theNeedResourceTableRoot) { + myNeedResourceTableRoot = theNeedResourceTableRoot; + } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/MemoryCacheService.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/MemoryCacheService.java index 9681f125613..5263333259d 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/MemoryCacheService.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/MemoryCacheService.java @@ -21,15 +21,16 @@ package ca.uhn.fhir.jpa.util; */ import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.api.model.TranslationQuery; import com.github.benmanes.caffeine.cache.Cache; import com.github.benmanes.caffeine.cache.Caffeine; +import org.apache.commons.lang3.tuple.Pair; +import org.hl7.fhir.instance.model.api.IIdType; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; import java.util.EnumMap; -import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -62,7 +63,7 @@ public class MemoryCacheService { case TAG_DEFINITION: case PERSISTENT_ID: case RESOURCE_LOOKUP: - case FORCED_ID: + case PID_TO_FORCED_ID: default: timeoutSeconds = 60; break; @@ -76,15 +77,18 @@ public class MemoryCacheService { public T get(CacheEnum theCache, K theKey, Function theSupplier) { + assert theCache.myKeyType.isAssignableFrom(theKey.getClass()); Cache cache = getCache(theCache); return cache.get(theKey, theSupplier); } public V getIfPresent(CacheEnum theCache, K theKey) { + assert theCache.myKeyType.isAssignableFrom(theKey.getClass()); return (V) getCache(theCache).getIfPresent(theKey); } public void put(CacheEnum theCache, K theKey, V theValue) { + assert theCache.myKeyType.isAssignableFrom(theKey.getClass()); getCache(theCache).put(theKey, theValue); } @@ -102,13 +106,21 @@ public class MemoryCacheService { public enum CacheEnum { - TAG_DEFINITION, - PERSISTENT_ID, - RESOURCE_LOOKUP, - FORCED_ID, - CONCEPT_TRANSLATION, - CONCEPT_TRANSLATION_REVERSE + TAG_DEFINITION(Pair.class), + PERSISTENT_ID(String.class), + RESOURCE_LOOKUP(String.class), + FORCED_ID_TO_PID(String.class), + PID_TO_FORCED_ID(Long.class), + CONCEPT_TRANSLATION(TranslationQuery.class), + MATCH_URL(String.class), + CONCEPT_TRANSLATION_REVERSE(TranslationQuery.class), + RESOURCE_CONDITIONAL_CREATE_VERSION(IIdType.class); + private final Class myKeyType; + + CacheEnum(Class theKeyType) { + myKeyType = theKeyType; + } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java index badb1502a49..afeb5ff28d1 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoCreatePlaceholdersR4Test.java @@ -348,7 +348,7 @@ public class FhirResourceDaoCreatePlaceholdersR4Test extends BaseJpaR4Test { Observation createdObs = myObservationDao.read(id); ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(createdObs)); - assertEquals("Patient/ABC", obsToCreate.getSubject().getReference()); + assertEquals("Patient/ABC", createdObs.getSubject().getReference()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java index 94a11c017fa..0cbe3e50191 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4QueryCountTest.java @@ -1,11 +1,13 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.SqlQuery; import ca.uhn.fhir.rest.api.SortSpec; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.util.BundleBuilder; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Bundle; @@ -25,11 +27,15 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4QueryCountTest.class); @@ -39,6 +45,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { myDaoConfig.setResourceMetaCountHardLimit(new DaoConfig().getResourceMetaCountHardLimit()); myDaoConfig.setIndexMissingFields(new DaoConfig().getIndexMissingFields()); myDaoConfig.setDeleteEnabled(new DaoConfig().isDeleteEnabled()); + myDaoConfig.setMatchUrlCache(new DaoConfig().getMatchUrlCache()); } @BeforeEach @@ -217,6 +224,80 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + @Test + public void testCreateWithClientAssignedId_CheckDisabledMode() { + when(mySrd.getHeader(eq(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK))).thenReturn(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK_DISABLED); + + myCaptureQueriesListener.clear(); + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("123"); + return myPatientDao.update(p, mySrd).getId().toUnqualified(); + }); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + myCaptureQueriesListener.logInsertQueriesForCurrentThread(); + assertEquals(4, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + } + + @Test + public void testUpdateWithClientAssignedId_DeletesDisabled() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + myDaoConfig.setDeleteEnabled(false); + + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("123"); + myPatientDao.update(p).getId().toUnqualified(); + }); + + + // Second time + + myCaptureQueriesListener.clear(); + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("456"); + myPatientDao.update(p).getId().toUnqualified(); + }); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); + assertEquals(1, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + myCaptureQueriesListener.logInsertQueriesForCurrentThread(); + assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + + // Third time (caches all loaded by now) + + myCaptureQueriesListener.clear(); + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("789"); + myPatientDao.update(p).getId().toUnqualified(); + }); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); + assertEquals(1, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); + myCaptureQueriesListener.logInsertQueriesForCurrentThread(); + assertEquals(1, myCaptureQueriesListener.getInsertQueriesForCurrentThread().size()); + myCaptureQueriesListener.logDeleteQueriesForCurrentThread(); + assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); + } + @Test public void testReferenceToForcedId() { @@ -343,7 +424,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { }); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); // Perform count, Search history table, resolve forced IDs - assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); assertNoPartitionSelectors(); myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); @@ -405,7 +486,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { }); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); // Perform count, Search history table, resolve forced IDs, load tags (x3) - assertEquals(6, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(5, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); myCaptureQueriesListener.logUpdateQueriesForCurrentThread(); assertEquals(0, myCaptureQueriesListener.getUpdateQueriesForCurrentThread().size()); myCaptureQueriesListener.logInsertQueriesForCurrentThread(); @@ -536,7 +617,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { myCaptureQueriesListener.logSelectQueriesForCurrentThread(); String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toLowerCase(); - assertEquals(2, StringUtils.countMatches(sql, "join"), sql); + assertEquals(1, StringUtils.countMatches(sql, "join"), sql); } @@ -587,6 +668,129 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size()); } + @Test + public void testTransactionWithConditionalCreate_MatchUrlCacheEnabled() { + myDaoConfig.setMatchUrlCache(true); + + Supplier bundleCreator = ()-> { + BundleBuilder bb = new BundleBuilder(myFhirCtx); + + Patient pt = new Patient(); + pt.setId(IdType.newRandomUuid()); + pt.addIdentifier().setSystem("http://foo").setValue("123"); + bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123"); + + Observation obs = new Observation(); + obs.setId(IdType.newRandomUuid()); + obs.setSubject(new Reference(pt.getId())); + bb.addTransactionCreateEntry(obs); + + return (Bundle) bb.getBundle(); + }; + + // Run once (creates both) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + assertEquals(1, myCaptureQueriesListener.countSelectQueries()); + assertEquals(5, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation")); + }); + + // Run a second time (creates a new observation, reuses the patient, should use cache) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + myCaptureQueriesListener.logSelectQueries(); + assertEquals(1, myCaptureQueriesListener.countSelectQueries()); + assertEquals(3, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation")); + }); + + // Run a third time (creates a new observation, reuses the patient, should use cache) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + assertEquals(0, myCaptureQueriesListener.countSelectQueries()); + assertEquals(3, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation")); + }); + + } + + @Test + public void testTransactionWithConditionalCreate_MatchUrlCacheNotEnabled() { + + Supplier bundleCreator = ()-> { + BundleBuilder bb = new BundleBuilder(myFhirCtx); + + Patient pt = new Patient(); + pt.setId(IdType.newRandomUuid()); + pt.addIdentifier().setSystem("http://foo").setValue("123"); + bb.addTransactionCreateEntry(pt).conditional("Patient?identifier=http://foo|123"); + + Observation obs = new Observation(); + obs.setId(IdType.newRandomUuid()); + obs.setSubject(new Reference(pt.getId())); + bb.addTransactionCreateEntry(obs); + + return (Bundle) bb.getBundle(); + }; + + // Run once (creates both) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + assertEquals(2, myCaptureQueriesListener.countSelectQueries()); + assertEquals(5, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation")); + }); + + // Run a second time (creates a new observation, reuses the patient, should use cache) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + myCaptureQueriesListener.logSelectQueries(); + assertEquals(2, myCaptureQueriesListener.countSelectQueries()); + assertEquals(3, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation")); + }); + + // Run a third time (creates a new observation, reuses the patient, should use cache) + + myCaptureQueriesListener.clear(); + mySystemDao.transaction(mySrd, bundleCreator.get()); + assertEquals(1, myCaptureQueriesListener.countSelectQueries()); + assertEquals(3, myCaptureQueriesListener.countInsertQueries()); + assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); + + runInTransaction(()->{ + List types = myResourceTableDao.findAll().stream().map(t -> t.getResourceType()).collect(Collectors.toList()); + assertThat(types, containsInAnyOrder("Patient", "Observation", "Observation", "Observation")); + }); + + } + @Test public void testTransactionWithMultipleReferences() { Bundle input = new Bundle(); @@ -1087,7 +1291,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { // Lookup the two existing IDs to make sure they are legit myCaptureQueriesListener.logSelectQueriesForCurrentThread(); - assertEquals(6, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(4, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); @@ -1146,7 +1350,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseJpaR4Test { // Lookup the two existing IDs to make sure they are legit myCaptureQueriesListener.logSelectQueriesForCurrentThread(); - assertEquals(6, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); + assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); assertEquals(2, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java index e68b4f378ff..76f785d193d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchCustomSearchParamTest.java @@ -857,7 +857,8 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, contains(p2id.getValue())); sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0); - assertThat(sql, countMatches(sql, "JOIN"), equalTo(1)); + assertThat(sql, countMatches(sql, "JOIN"), equalTo(0)); + assertThat(sql, countMatches(sql, "SELECT"), equalTo(1)); assertThat(sql, countMatches(sql, "t0.SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1)); assertThat(sql, countMatches(sql, "t0.TARGET_RESOURCE_ID = '"), equalTo(1)); @@ -869,7 +870,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, contains(p2id.getValue())); sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0); - assertThat(sql, countMatches(sql, "JOIN"), equalTo(2)); + assertThat(sql, countMatches(sql, "JOIN"), equalTo(1)); assertThat(sql, countMatches(sql, "SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1)); assertThat(sql, countMatches(sql, "HASH_NORM_PREFIX = '"), equalTo(39)); assertThat(sql, countMatches(sql, "SP_VALUE_NORMALIZED LIKE "), equalTo(39)); @@ -882,7 +883,7 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test foundResources = toUnqualifiedVersionlessIdValues(results); assertThat(foundResources, containsInAnyOrder(appid.getValue())); sql = myCaptureQueriesListener.logSelectQueriesForCurrentThread(0); - assertThat(sql, countMatches(sql, "JOIN"), equalTo(3)); + assertThat(sql, countMatches(sql, "JOIN"), equalTo(2)); assertThat(sql, countMatches(sql, "SRC_PATH = 'Appointment.participant.actor.where(resolve() is Patient)'"), equalTo(1)); assertThat(sql, countMatches(sql, "SRC_PATH = 'Patient.extension('http://acme.org/sibling')'"), equalTo(1)); assertThat(sql, countMatches(sql, "SP_VALUE_NORMALIZED LIKE 'P1%'"), equalTo(39)); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java index c208dedcb1d..a3ec8f94745 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java @@ -3697,7 +3697,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { String searchQuery = queries.get(0); assertEquals(1, countMatches(searchQuery.toUpperCase(), "HFJ_SPIDX_TOKEN"), searchQuery); - assertEquals(2, countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery); + assertEquals(1, countMatches(searchQuery.toUpperCase(), "LEFT OUTER JOIN"), searchQuery); assertEquals(2, countMatches(searchQuery.toUpperCase(), "RES_UPDATED"), searchQuery); } @@ -4119,7 +4119,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(toUnqualifiedVersionlessIdValues(found).toString(), toUnqualifiedVersionlessIdValues(found), contains(id1)); String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); - assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); + assertEquals(0, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); assertEquals(0, countMatches(searchQuery.toLowerCase(), "partition"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value"), searchQuery); @@ -4405,7 +4405,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(patients.toString(), patients, contains(obsId1)); String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search query:\n{}", searchQuery); - assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); + assertEquals(0, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_high_date_ordinal >= '20200605'"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "t0.sp_value_low_date_ordinal <= '20200606'"), searchQuery); } @@ -4440,7 +4440,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertThat(patients.toString(), patients, containsInAnyOrder(obsId3, obsId4)); String searchQuery = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("Search query:\n{}", searchQuery); - assertEquals(1, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); + assertEquals(0, countMatches(searchQuery.toLowerCase(), "join"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "hash_identity"), searchQuery); assertEquals(1, countMatches(searchQuery.toLowerCase(), "sp_value_low"), searchQuery); } @@ -5465,7 +5465,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test { assertEquals(1, outcome.sizeOrThrowNpe()); String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); - assertEquals(4, countMatches(searchSql, "JOIN")); + assertEquals(3, countMatches(searchSql, "JOIN")); assertEquals(1, countMatches(searchSql, "SELECT")); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 4c6bfcc0644..c8510deb149 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -790,7 +790,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { String selectQuery = myCaptureQueriesListener.getSelectQueries().get(0).getSql(true, true); ourLog.info(selectQuery); - assertEquals(2, StringUtils.countMatches(selectQuery, "JOIN")); + assertEquals(1, StringUtils.countMatches(selectQuery, "JOIN")); assertEquals(1, StringUtils.countMatches(selectQuery, "SELECT")); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchSqlTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchSqlTest.java new file mode 100644 index 00000000000..7462cdc8c8d --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchSqlTest.java @@ -0,0 +1,47 @@ +package ca.uhn.fhir.jpa.dao.r4; + +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import org.hl7.fhir.r4.model.Patient; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class FhirResourceDaoR4SearchSqlTest extends BaseJpaR4Test { + + /** + * One regular search params - Doesn't need HFJ_RESOURCE as root + */ + @Test + public void testSingleRegularSearchParam() { + + myCaptureQueriesListener.clear(); + SearchParameterMap map = SearchParameterMap.newSynchronous(Patient.SP_NAME, new StringParam("FOO")); + myPatientDao.search(map); + assertEquals(1, myCaptureQueriesListener.countSelectQueries()); + String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false); + assertEquals("SELECT t0.RES_ID FROM HFJ_SPIDX_STRING t0 WHERE ((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?))", sql); + + } + + /** + * Two regular search params - Should use HFJ_RESOURCE as root + */ + @Test + public void testTwoRegularSearchParams() { + + myCaptureQueriesListener.clear(); + SearchParameterMap map = SearchParameterMap.newSynchronous() + .add(Patient.SP_NAME, new StringParam("FOO")) + .add(Patient.SP_GENDER, new TokenParam("a", "b")); + myPatientDao.search(map); + assertEquals(1, myCaptureQueriesListener.countSelectQueries()); + String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(false, false); + assertEquals("SELECT t1.RES_ID FROM HFJ_RESOURCE t1 LEFT OUTER JOIN HFJ_SPIDX_STRING t0 ON (t1.RES_ID = t0.RES_ID) LEFT OUTER JOIN HFJ_SPIDX_TOKEN t2 ON (t1.RES_ID = t2.RES_ID) WHERE (((t0.HASH_NORM_PREFIX = ?) AND (t0.SP_VALUE_NORMALIZED LIKE ?)) AND (t2.HASH_SYS_AND_VALUE = ?))", sql); + + + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java index 4fcbdcd2a24..110c4177aed 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UpdateTest.java @@ -1,21 +1,25 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.jpa.api.config.DaoConfig; -import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable; import ca.uhn.fhir.jpa.model.entity.ResourceTable; +import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; 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 ca.uhn.fhir.util.BundleBuilder; 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.hl7.fhir.r4.model.CanonicalType; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.Coding; @@ -23,14 +27,15 @@ import org.hl7.fhir.r4.model.ContactPoint; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.InstantType; import org.hl7.fhir.r4.model.Meta; +import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Patient; +import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Resource; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Date; @@ -39,6 +44,7 @@ import java.util.List; import java.util.Set; import java.util.TimeZone; import java.util.UUID; +import java.util.function.Supplier; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; @@ -53,7 +59,9 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.when; public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4UpdateTest.class); @@ -71,6 +79,30 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { myInterceptorRegistry.registerInterceptor(myInterceptor); } + @Test + public void testCreateWithClientAssignedId_CheckDisabledMode_AlreadyExists() { + when(mySrd.getHeader(eq(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK))).thenReturn(JpaConstants.HEADER_UPSERT_EXISTENCE_CHECK_DISABLED); + + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("123"); + return myPatientDao.update(p, mySrd).getId().toUnqualified(); + }); + try { + runInTransaction(() -> { + Patient p = new Patient(); + p.setId("AAA"); + p.getMaritalStatus().setText("123"); + return myPatientDao.update(p, mySrd).getId().toUnqualified(); + }); + fail(); + } catch (ResourceVersionConflictException e) { + assertThat(e.getMessage(), containsString("It can also happen when a request disables the Upsert Existence Check.")); + } + } + + @Test public void testCreateAndUpdateWithoutRequest() { String methodName = "testUpdateByUrl"; @@ -176,7 +208,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { p.addName().setFamily("FAMILY1"); IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless(); - runInTransaction(()->{ + runInTransaction(() -> { myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashIdentity = null").executeUpdate(); myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashExact = null").executeUpdate(); myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashNormalizedPrefix = null").executeUpdate(); @@ -195,7 +227,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test { SearchParameterMap map = new SearchParameterMap(); map.setLoadSynchronous(true); map.add(Patient.SP_FAMILY, new StringParam("FAMILY2")); - Patient newPatient = (Patient) myPatientDao.search(map).getResources(0,1).get(0); + Patient newPatient = (Patient) myPatientDao.search(map).getResources(0, 1).get(0); assertEquals("FAMILY2", newPatient.getName().get(0).getFamily()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java index ec689d1a673..334c04f027c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4VersionedReferenceTest.java @@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.rest.api.server.IBundleProvider; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.util.BundleBuilder; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -183,6 +184,9 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test { myEncounterDao.create(encounter); } + // Verify Patient Version + assertEquals("2", myPatientDao.search(SearchParameterMap.newSynchronous("active", new TokenParam("false"))).getResources(0,1).get(0).getIdElement().getVersionIdPart()); + BundleBuilder builder = new BundleBuilder(myFhirCtx); Patient patient = new Patient(); @@ -215,6 +219,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test { // Read back and verify that reference is now versioned observation = myObservationDao.read(observationId); assertEquals(patientId.getValue(), observation.getSubject().getReference()); + assertEquals("2", observation.getSubject().getReferenceElement().getVersionIdPart()); assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference()); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java index a7d4f7a5278..c588c467a3d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirSystemDaoR4Test.java @@ -71,6 +71,7 @@ import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; +import javax.annotation.Nonnull; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -85,6 +86,7 @@ import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.emptyString; @@ -1119,6 +1121,23 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); assertEquals("1", respEntry.getResponse().getEtag()); + /* + * Third time should not update + */ + + request = new Bundle(); + o = new Observation(); + o.getCode().setText("Some Observation"); + request.addEntry().setResource(o).getRequest().setMethod(HTTPVerb.POST).setIfNoneExist("Observation?_lastUpdated=gt2011-01-01"); + resp = mySystemDao.transaction(mySrd, request); + assertEquals(1, resp.getEntry().size()); + + respEntry = resp.getEntry().get(0); + assertEquals(Constants.STATUS_HTTP_200_OK + " OK", respEntry.getResponse().getStatus()); + assertThat(respEntry.getResponse().getLocation(), containsString("Observation/")); + assertThat(respEntry.getResponse().getLocation(), endsWith("/_history/1")); + assertEquals("1", respEntry.getResponse().getEtag()); + } @Test @@ -2580,6 +2599,56 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { } + /** + * DAOs can't handle references where Reference.setResource + * is set but not Reference.setReference so make sure + * we block this so it doesn't get used accidentally. + */ + @Test + public void testTransactionWithResourceReferenceInsteadOfIdReferenceBlocked() { + + Bundle input = createBundleWithConditionalCreateReferenceByResource(); + mySystemDao.transaction(mySrd, input); + + // Fails the second time + try { + input = createBundleWithConditionalCreateReferenceByResource(); + mySystemDao.transaction(mySrd, input); + fail(); + } catch (InternalErrorException e) { + assertEquals("References by resource with no reference ID are not supported in DAO layer", e.getMessage()); + } + + } + + @Nonnull + private Bundle createBundleWithConditionalCreateReferenceByResource() { + Bundle input = new Bundle(); + input.setType(BundleType.TRANSACTION); + + Patient p = new Patient(); + p.setId(IdType.newRandomUuid()); + p.addIdentifier().setSystem("foo").setValue("bar"); + input.addEntry() + .setFullUrl(p.getId()) + .setResource(p) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Patient") + .setIfNoneExist("Patient?identifier=foo|bar"); + + Observation o1 = new Observation(); + o1.setId(IdType.newRandomUuid()); + o1.setStatus(ObservationStatus.FINAL); + o1.getSubject().setResource(p); // Not allowed + input.addEntry() + .setFullUrl(o1.getId()) + .setResource(o1) + .getRequest() + .setMethod(HTTPVerb.POST) + .setUrl("Observation"); + return input; + } @Test public void testDeleteInTransactionShouldFailWhenReferencesExist() { @@ -2663,7 +2732,7 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { Observation o = new Observation(); o.setId(IdType.newRandomUuid()); o.setStatus(ObservationStatus.FINAL); - o.getSubject().setResource(p); + o.getSubject().setReference(p.getId()); b.addEntry() .setFullUrl(o.getId()) .setResource(o) @@ -4037,4 +4106,5 @@ public class FhirSystemDaoR4Test extends BaseJpaR4SystemTest { } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java index 13cfc0ce1f9..fc00df065fb 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/PartitioningSqlR4Test.java @@ -2631,7 +2631,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { List ids = toUnqualifiedIdValues(results); assertThat(ids, contains(id.withVersion("2").getValue(), id.withVersion("1").getValue())); - assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); // Resolve resource String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); @@ -2648,12 +2648,6 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { ourLog.info("SQL:{}", searchSql); assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase()); assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase()); - - // Fetch history resource - searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3).getSql(true, true); - ourLog.info("SQL:{}", searchSql); - assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase()); - assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase()); } @Test @@ -2694,9 +2688,9 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { List ids = toUnqualifiedIdValues(results); assertThat(ids, contains(id.withVersion("2").getValue(), id.withVersion("1").getValue())); - assertEquals(4, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); - // Resolve resource + // Fetch history resource String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("SQL:{}", sql); assertEquals(0, countMatches(sql, "PARTITION_ID=")); @@ -2710,11 +2704,6 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true); ourLog.info("SQL:{}", sql); assertEquals(0, countMatches(sql, "PARTITION_ID=")); - - // Fetch history resource - sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(3).getSql(true, true); - ourLog.info("SQL:{}", sql); - assertEquals(0, countMatches(sql, "PARTITION_ID=")); } @Test @@ -2764,7 +2753,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { List ids = toUnqualifiedIdValues(results); assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue())); - assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); // Count ourLog.info("SQL:{}", myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true)); @@ -2777,12 +2766,6 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { ourLog.info("SQL:{}", sql); assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql); - // Fetch history resource - sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, false); - sql = sql.replace(" ", "").toUpperCase(); - ourLog.info("SQL:{}", sql); - assertEquals(0, countMatches(sql, "PARTITION_ID="), sql); - assertEquals(0, countMatches(sql, "PARTITION_IDIN"), sql); } @Test @@ -2802,20 +2785,15 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { List ids = toUnqualifiedIdValues(results); assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue())); - assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); // Count String searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true); ourLog.info("SQL:{}", searchSql); assertEquals(1, countMatches(searchSql, "PARTITION_ID is null"), searchSql); - // Fetch history - searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true); - ourLog.info("SQL:{}", searchSql); - assertEquals(1, countMatches(searchSql, "PARTITION_ID is null"), searchSql); - // Fetch history resource - searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true); + searchSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true); ourLog.info("SQL:{}", searchSql); assertEquals(0, countMatches(searchSql, "PARTITION_ID="), searchSql.replace(" ", "").toUpperCase()); assertEquals(0, countMatches(searchSql, "PARTITION_IDIN"), searchSql.replace(" ", "").toUpperCase()); @@ -2885,7 +2863,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { List ids = toUnqualifiedIdValues(results); assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue())); - assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); // Count String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false).toUpperCase(); @@ -2893,15 +2871,10 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertEquals(1, countMatches(sql, "COUNT("), sql); assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql); - // Fetch history resources + // History sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, false).toUpperCase(); ourLog.info("SQL:{}", sql); assertEquals(1, countMatches(sql, "PARTITION_ID IN ('1')"), sql); - - // Resolve forced ID - sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, false).toUpperCase(); - ourLog.info("SQL:{}", sql); - assertEquals(0, countMatches(sql, "PARTITION_ID IN ('1')"), sql); } @@ -2923,7 +2896,7 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { assertThat(ids, contains(id1B.withVersion("1").getValue(), id1A.withVersion("1").getValue())); myCaptureQueriesListener.logSelectQueriesForCurrentThread(); - assertEquals(3, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); + assertEquals(2, myCaptureQueriesListener.getSelectQueriesForCurrentThread().size()); // Resolve resource String sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, true).toUpperCase(); @@ -2934,10 +2907,6 @@ public class PartitioningSqlR4Test extends BasePartitioningR4Test { sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(1).getSql(true, true).toUpperCase(); assertEquals(1, countMatches(sql, "PARTITION_ID IS NULL")); - // Resolve forced IDs - sql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(2).getSql(true, true).toUpperCase(); - assertEquals(1, countMatches(sql, "FORCEDID0_.RESOURCE_PID IN"), sql); - assertEquals(0, countMatches(sql, "PARTITION_ID IS NULL"), sql); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderHasParamR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderHasParamR4Test.java index e79994980ee..bf672bbde5e 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderHasParamR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderHasParamR4Test.java @@ -616,6 +616,7 @@ public class ResourceProviderHasParamR4Test extends BaseResourceProviderR4Test { List notInListQueries = new ArrayList<>(); for (String query : queries) { + ourLog.info("Query: {}", query); if (query.contains("RES_ID NOT IN")) notInListQueries.add(query); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java index eda0afa7e9c..45e93625d7f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ServerCapabilityStatementProviderJpaR4Test.java @@ -132,7 +132,9 @@ public class ServerCapabilityStatementProviderJpaR4Test extends BaseResourceProv .stream() .map(t -> t.getCode()) .collect(Collectors.toList()); - assertThat(formats.toString(), formats, containsInAnyOrder( + assertThat(formats.toString(), formats, hasItems( + "application/x-turtle", + "ttl", "application/fhir+xml", "application/fhir+json", "json", diff --git a/hapi-fhir-jpaserver-batch/pom.xml b/hapi-fhir-jpaserver-batch/pom.xml index d3df9443653..74594c4e20a 100644 --- a/hapi-fhir-jpaserver-batch/pom.xml +++ b/hapi-fhir-jpaserver-batch/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-cql/pom.xml b/hapi-fhir-jpaserver-cql/pom.xml index 8f5317c6e83..1c4173e2a53 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -149,13 +149,13 @@ ca.uhn.hapi.fhir hapi-fhir-test-utilities - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT test ca.uhn.hapi.fhir hapi-fhir-jpaserver-test-utilities - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT test diff --git a/hapi-fhir-jpaserver-mdm/pom.xml b/hapi-fhir-jpaserver-mdm/pom.xml index f49c0de98fa..6d155eb5fe6 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -55,13 +55,13 @@ ca.uhn.hapi.fhir hapi-fhir-test-utilities - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT test ca.uhn.hapi.fhir hapi-fhir-jpaserver-test-utilities - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT test diff --git a/hapi-fhir-jpaserver-migrate/pom.xml b/hapi-fhir-jpaserver-migrate/pom.xml index aa549ab2385..8f1d828003e 100644 --- a/hapi-fhir-jpaserver-migrate/pom.xml +++ b/hapi-fhir-jpaserver-migrate/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-model/pom.xml b/hapi-fhir-jpaserver-model/pom.xml index 189bf7f38da..77e0bff6a25 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml @@ -132,6 +132,12 @@ quartz + + + com.github.ben-manes.caffeine + caffeine + + javax.annotation 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 4101d2504b7..043807c1212 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 @@ -28,6 +28,7 @@ import org.hl7.fhir.dstu2.model.Subscription; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.DateTimeType; +import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import java.util.Arrays; import java.util.Collections; @@ -382,6 +383,8 @@ public class ModelConfig { return this; } + + /** * This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted * to the server matching these types will be activated. diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 2d7c10fa796..dd18c33efe9 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -227,6 +227,8 @@ public class JpaConstants { * Parameter for the $expand operation */ public static final String OPERATION_EXPAND_PARAM_INCLUDE_HIERARCHY = "includeHierarchy"; + public static final String HEADER_UPSERT_EXISTENCE_CHECK = "X-Upsert-Extistence-Check"; + public static final String HEADER_UPSERT_EXISTENCE_CHECK_DISABLED = "disabled"; /** * Non-instantiable diff --git a/hapi-fhir-jpaserver-searchparam/pom.xml b/hapi-fhir-jpaserver-searchparam/pom.xml index 54c7f179c7b..a9e7f5579a0 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java index d671c3cb870..01c0cf18623 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/MatchUrlService.java @@ -23,9 +23,9 @@ package ca.uhn.fhir.jpa.searchparam; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.util.JpaConstants; import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil; -import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.model.api.IQueryParameterAnd; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.Include; @@ -35,6 +35,7 @@ import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.ParameterUtil; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.util.ISearchParamRegistry; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; import com.google.common.collect.ArrayListMultimap; @@ -52,6 +53,8 @@ public class MatchUrlService { private FhirContext myContext; @Autowired private ISearchParamRegistry mySearchParamRegistry; + @Autowired + private ModelConfig myModelConfig; public SearchParameterMap translateMatchUrl(String theMatchUrl, RuntimeResourceDefinition theResourceDefinition, Flag... theFlags) { SearchParameterMap paramMap = new SearchParameterMap(); diff --git a/hapi-fhir-jpaserver-subscription/pom.xml b/hapi-fhir-jpaserver-subscription/pom.xml index cd087657fa9..ae6e855c207 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 5a330441e24..c8c3610cb7b 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml index 407bad660b7..62f60a3ce07 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml @@ -169,7 +169,7 @@ ca.uhn.hapi.fhir hapi-fhir-converter - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml index 213863947bf..91e69548851 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml index 59d85a5e228..f3745b85ddb 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml index b0334c6fbbc..7002a96b7f4 100644 --- a/hapi-fhir-server/pom.xml +++ b/hapi-fhir-server/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml index db762d268b7..feaf0480a11 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 d0ef59739a9..b1c84610732 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 c7dba5afe65..c8d01554e3d 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 fd0b2400bd2..89a5f282432 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 23dc4f4cc96..c3e1f680b7e 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 37bb5fbff89..3b61d351c2e 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml index e3050e67351..89781d1605d 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml index 143a1fd9ffc..0abc1c9c5c7 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml index eba9cfc2801..e4aa0b3ad3f 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml index f9d795a9068..1c333c06790 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 5f1386f1496..35d84726b01 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml index 6bdf2b268dd..25ba5a5ef8a 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml index 3a1dbc48cb6..2f9198a62df 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml index 609c1ab8515..0232b3ad8e2 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml index ee6382cca38..a496115827f 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/hapi-fhir-validation-resources-dstu2.1/pom.xml b/hapi-fhir-validation-resources-dstu2.1/pom.xml index 3af7e1b74b1..40880fd7d29 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 53941c421b0..114bba57fe3 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 9570b03358f..ecd58e259ca 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 188381def8a..2af07436a9b 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 79b31674df1..896f7ffe224 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-fhir-validation/pom.xml b/hapi-fhir-validation/pom.xml index 07a69c1d9c2..31452178b20 100644 --- a/hapi-fhir-validation/pom.xml +++ b/hapi-fhir-validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-deployable-pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../hapi-deployable-pom/pom.xml diff --git a/hapi-tinder-plugin/pom.xml b/hapi-tinder-plugin/pom.xml index 6be3a8a978f..16bd9f8943a 100644 --- a/hapi-tinder-plugin/pom.xml +++ b/hapi-tinder-plugin/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml @@ -58,37 +58,37 @@ ca.uhn.hapi.fhir hapi-fhir-structures-dstu3 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-hl7org-dstu2 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-r4 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-structures-r5 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu2 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-dstu3 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ca.uhn.hapi.fhir hapi-fhir-validation-resources-r4 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT org.apache.velocity diff --git a/hapi-tinder-test/pom.xml b/hapi-tinder-test/pom.xml index 247b0328ab6..71dd71d7fa7 100644 --- a/hapi-tinder-test/pom.xml +++ b/hapi-tinder-test/pom.xml @@ -4,7 +4,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 1a6e3c15123..c3a6ac010fa 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ ca.uhn.hapi.fhir hapi-fhir pom - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT HAPI-FHIR An open-source implementation of the FHIR specification in Java. https://hapifhir.io diff --git a/restful-server-example/pom.xml b/restful-server-example/pom.xml index dfb8121f1a7..4aea210e143 100644 --- a/restful-server-example/pom.xml +++ b/restful-server-example/pom.xml @@ -8,7 +8,7 @@ ca.uhn.hapi.fhir hapi-fhir - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../pom.xml diff --git a/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml b/tests/hapi-fhir-base-test-jaxrsserver-kotlin/pom.xml index 5308513a49d..6cf7d6b242c 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 4c39a242c15..32c4b7d9126 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-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 429c414d48a..c806594d502 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 - 5.4.0-PRE8-SNAPSHOT + 5.4.0-PRE9-SNAPSHOT ../../pom.xml