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.fhirhapi-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.fhirhapi-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.fhirhapi-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.0ca.uhn.hapi.fhirhapi-fhir-bom
- 5.4.0-PRE8-SNAPSHOT
+ 5.4.0-PRE9-SNAPSHOTpomHAPI FHIR BOMca.uhn.hapi.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-deployable-pom
- 5.4.0-PRE8-SNAPSHOT
+ 5.4.0-PRE9-SNAPSHOT../hapi-deployable-pom/pom.xml
@@ -83,13 +83,13 @@
ca.uhn.hapi.fhirhapi-fhir-structures-dstu2
- 5.4.0-PRE8-SNAPSHOT
+ 5.4.0-PRE9-SNAPSHOTcompileca.uhn.hapi.fhirhapi-fhir-jpaserver-subscription
- 5.4.0-PRE8-SNAPSHOT
+ 5.4.0-PRE9-SNAPSHOTcompile
@@ -106,7 +106,7 @@
ca.uhn.hapi.fhirhapi-fhir-testpage-overlay
- 5.4.0-PRE8-SNAPSHOT
+ 5.4.0-PRE9-SNAPSHOTclasses
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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.fhirhapi-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.caffeinecaffeine
@@ -604,13 +604,19 @@
org.jetbrainsannotations
-
- 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