Transaction with delete then update should not fail (#4831)
* Fixed * Test fixes * Add test * Ongoing work * Work on xactx * Cleanup * Changelog cleanup * Resolve fixme * Rework broken APIs * Version bump * Add license headers * License header update * License * rk on fixes * Test fixes * Address review comments * Test fixes * Add license headers * License header
This commit is contained in:
parent
8cf14c8c0e
commit
483ddca3be
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -123,7 +123,7 @@ public abstract class BasePrimitive<T> extends BaseIdentifiableElement implement
|
|||
myStringValue = null;
|
||||
} else {
|
||||
// NB this might be null
|
||||
myStringValue = encode(myCoercedValue);
|
||||
myStringValue = encode(myCoercedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -249,6 +249,22 @@ public class BundleBuilder {
|
|||
return new CreateBuilder(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry containing a delete (DELETE) request.
|
||||
* Also sets the Bundle.type value to "transaction" if it is not already set.
|
||||
* <p>
|
||||
* Note that the resource is only used to extract its ID and type, and the body of the resource is not included in the entry,
|
||||
*
|
||||
* @param theCondition The conditional URL, e.g. "Patient?identifier=foo|bar"
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public DeleteBuilder addTransactionDeleteConditionalEntry(String theCondition) {
|
||||
Validate.notBlank(theCondition, "theCondition must not be blank");
|
||||
|
||||
setBundleField("type", "transaction");
|
||||
return addDeleteEntry(theCondition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry containing a delete (DELETE) request.
|
||||
* Also sets the Bundle.type value to "transaction" if it is not already set.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-bom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<packaging>pom</packaging>
|
||||
<name>HAPI FHIR BOM</name>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Docs
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.hapi.fhir.docs;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 4831
|
||||
title: "When performing a FHIR transaction containing both a conditional delete as well as a
|
||||
conditional create/update for the same resource, the resource was left in an inconsistent
|
||||
state. This has been corrected. Thanks to Laxman Singh for raising this issue."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: perf
|
||||
issue: 4831
|
||||
title: "Conditional deletes that delete multiple resources at once have been optimized to perform
|
||||
fewer SQL select statements, which should improve performance on large deletes."
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- item:
|
||||
type: "add"
|
||||
title: "The version of a few dependencies have been bumped to the latest versions
|
||||
(dependent HAPI modules listed in brackets):
|
||||
<ul>
|
||||
<li>Hibernate ORM (JPA): 5.6.12.Final -> 5.6.15.Final</li>
|
||||
</ul>"
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -536,20 +536,16 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
|
||||
|
||||
void incrementId(T theResource, ResourceTable theSavedEntity, IIdType theResourceId) {
|
||||
String newVersion;
|
||||
long newVersionLong;
|
||||
if (theResourceId == null || theResourceId.getVersionIdPart() == null) {
|
||||
newVersion = "1";
|
||||
newVersionLong = 1;
|
||||
theSavedEntity.initializeVersion();
|
||||
} else {
|
||||
newVersionLong = theResourceId.getVersionIdPartAsLong() + 1;
|
||||
newVersion = Long.toString(newVersionLong);
|
||||
theSavedEntity.markVersionUpdatedInCurrentTransaction();
|
||||
}
|
||||
|
||||
assert theResourceId != null;
|
||||
String newVersion = Long.toString(theSavedEntity.getVersion());
|
||||
IIdType newId = theResourceId.withVersion(newVersion);
|
||||
theResource.getIdElement().setValue(newId.getValue());
|
||||
theSavedEntity.setVersion(newVersionLong);
|
||||
}
|
||||
|
||||
public boolean isLogicalReference(IIdType theId) {
|
||||
|
@ -1090,9 +1086,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
return entity;
|
||||
}
|
||||
|
||||
if (theUpdateVersion) {
|
||||
long newVersion = entity.getVersion() + 1;
|
||||
entity.setVersion(newVersion);
|
||||
if (entity.getId() != null && theUpdateVersion) {
|
||||
entity.markVersionUpdatedInCurrentTransaction();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1295,6 +1290,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
|
||||
private void createHistoryEntry(RequestDetails theRequest, IBaseResource theResource, ResourceTable theEntity, EncodedResource theChanged) {
|
||||
boolean versionedTags = getStorageSettings().getTagStorageMode() == JpaStorageSettings.TagStorageModeEnum.VERSIONED;
|
||||
|
||||
final ResourceHistoryTable historyEntry = theEntity.toHistory(versionedTags);
|
||||
historyEntry.setEncoding(theChanged.getEncoding());
|
||||
historyEntry.setResource(theChanged.getResourceBinary());
|
||||
|
|
|
@ -35,6 +35,7 @@ import ca.uhn.fhir.interceptor.model.RequestPartitionId;
|
|||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.api.dao.ReindexOutcome;
|
||||
import ca.uhn.fhir.jpa.api.dao.ReindexParameters;
|
||||
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
|
||||
|
@ -199,9 +200,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
private TransactionTemplate myTxTemplate;
|
||||
@Autowired
|
||||
private UrlPartitioner myUrlPartitioner;
|
||||
|
||||
@Autowired
|
||||
private ResourceSearchUrlSvc myResourceSearchUrlSvc;
|
||||
@Autowired
|
||||
private IFhirSystemDao<?, ?> mySystemDao;
|
||||
|
||||
public static <T extends IBaseResource> T invokeStoragePreShowResources(IInterceptorBroadcaster theInterceptorBroadcaster, RequestDetails theRequest, T retVal) {
|
||||
if (CompositeInterceptorBroadcaster.hasHooks(Pointcut.STORAGE_PRESHOW_RESOURCES, theInterceptorBroadcaster, theRequest)) {
|
||||
|
@ -263,12 +265,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
*/
|
||||
@Override
|
||||
public DaoMethodOutcome create(final T theResource) {
|
||||
return create(theResource, null, true, new TransactionDetails(), null);
|
||||
return create(theResource, null, true, null, new TransactionDetails());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome create(final T theResource, RequestDetails theRequestDetails) {
|
||||
return create(theResource, null, true, new TransactionDetails(), theRequestDetails);
|
||||
return create(theResource, null, true, theRequestDetails, new TransactionDetails());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -281,11 +283,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
@Override
|
||||
public DaoMethodOutcome create(final T theResource, String theIfNoneExist, RequestDetails theRequestDetails) {
|
||||
return create(theResource, theIfNoneExist, true, new TransactionDetails(), theRequestDetails);
|
||||
return create(theResource, theIfNoneExist, true, theRequestDetails, new TransactionDetails());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
|
||||
public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails) {
|
||||
RequestPartitionId requestPartitionId = myRequestPartitionHelperService.determineCreatePartitionForRequest(theRequestDetails, theResource, getResourceName());
|
||||
return myTransactionService
|
||||
.withRequest(theRequestDetails)
|
||||
|
@ -340,7 +342,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
entity.setResourceType(toResourceName(theResource));
|
||||
entity.setPartitionId(PartitionablePartitionId.toStoragePartition(theRequestPartitionId, myPartitionSettings));
|
||||
entity.setCreatedByMatchUrl(theMatchUrl);
|
||||
entity.setVersion(1);
|
||||
entity.initializeVersion();
|
||||
|
||||
if (isNotBlank(theMatchUrl) && theProcessMatchUrl) {
|
||||
Set<JpaPid> match = myMatchResourceUrlService.processMatchUrl(theMatchUrl, myResourceType, theTransactionDetails, theRequest);
|
||||
|
@ -348,19 +350,51 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "transactionOperationWithMultipleMatchFailure", "CREATE", theMatchUrl, match.size());
|
||||
throw new PreconditionFailedException(Msg.code(958) + msg);
|
||||
} else if (match.size() == 1) {
|
||||
JpaPid pid = match.iterator().next();
|
||||
|
||||
Supplier<LazyDaoMethodOutcome.EntityAndResource> entitySupplier = () -> {
|
||||
return myTxTemplate.execute(tx -> {
|
||||
/*
|
||||
* Ok, so we've found a single PID that matches the conditional URL.
|
||||
* That's good, there are two possibilities below.
|
||||
*/
|
||||
|
||||
JpaPid pid = match.iterator().next();
|
||||
if (theTransactionDetails.getDeletedResourceIds().contains(pid)) {
|
||||
|
||||
/*
|
||||
* If the resource matching the given match URL has already been
|
||||
* deleted within this transaction. This is a really rare case, since
|
||||
* it means the client has performed a FHIR transaction with both
|
||||
* a delete and a create on the same conditional URL. This is rare
|
||||
* but allowed, and means that it's now ok to create a new one resource
|
||||
* matching the conditional URL since we'll be deleting any existing
|
||||
* index rows on the existing resource as a part of this transaction.
|
||||
* We can also un-resolve the previous match URL in the TransactionDetails
|
||||
* since we'll resolve it to the new resource ID below
|
||||
*/
|
||||
|
||||
myMatchResourceUrlService.unresolveMatchUrl(theTransactionDetails, getResourceName(), theMatchUrl);
|
||||
|
||||
} else {
|
||||
|
||||
/*
|
||||
* This is the normal path where the conditional URL matched exactly
|
||||
* one resource, so we won't be creating anything but instead
|
||||
* just returning the existing ID. We now have a PID for the matching
|
||||
* resource, but we haven't loaded anything else (e.g. the forced ID
|
||||
* or the resource body aren't yet loaded from the DB). We're going to
|
||||
* return a LazyDaoOutcome with two lazy loaded providers for loading the
|
||||
* entity and the forced ID since we can avoid these extra SQL loads
|
||||
* unless we know we're actually going to use them. For example, if
|
||||
* the client has specified "Prefer: return=minimal" then we won't be
|
||||
* needing the load the body.
|
||||
*/
|
||||
|
||||
Supplier<LazyDaoMethodOutcome.EntityAndResource> entitySupplier = () -> myTxTemplate.execute(tx -> {
|
||||
ResourceTable foundEntity = myEntityManager.find(ResourceTable.class, pid.getId());
|
||||
IBaseResource resource = myJpaStorageResourceParser.toResource(foundEntity, false);
|
||||
theResource.setId(resource.getIdElement().getValue());
|
||||
return new LazyDaoMethodOutcome.EntityAndResource(foundEntity, resource);
|
||||
});
|
||||
};
|
||||
|
||||
Supplier<IIdType> idSupplier = () -> {
|
||||
return myTxTemplate.execute(tx -> {
|
||||
Supplier<IIdType> idSupplier = () -> myTxTemplate.execute(tx -> {
|
||||
IIdType retVal = myIdHelperService.translatePidIdToForcedId(myFhirContext, myResourceName, pid);
|
||||
if (!retVal.hasVersionIdPart()) {
|
||||
Long version = myMemoryCacheService.getIfPresent(MemoryCacheService.CacheEnum.RESOURCE_CONDITIONAL_CREATE_VERSION, pid.getId());
|
||||
|
@ -376,13 +410,13 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
return retVal;
|
||||
});
|
||||
};
|
||||
|
||||
DaoMethodOutcome outcome = toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true);
|
||||
StorageResponseCodeEnum responseCode = StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH;
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalWithMatch", w.getMillisAndRestart(), UrlUtil.sanitizeUrlPart(theMatchUrl));
|
||||
outcome.setOperationOutcome(createInfoOperationOutcome(msg, responseCode));
|
||||
return outcome;
|
||||
DaoMethodOutcome outcome = toMethodOutcomeLazy(theRequest, pid, entitySupplier, idSupplier).setCreated(false).setNop(true);
|
||||
StorageResponseCodeEnum responseCode = StorageResponseCodeEnum.SUCCESSFUL_CREATE_WITH_CONDITIONAL_MATCH;
|
||||
String msg = getContext().getLocalizer().getMessageSanitized(BaseStorageDao.class, "successfulCreateConditionalWithMatch", w.getMillisAndRestart(), UrlUtil.sanitizeUrlPart(theMatchUrl));
|
||||
outcome.setOperationOutcome(createInfoOperationOutcome(msg, responseCode));
|
||||
return outcome;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -617,12 +651,15 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
throw new ResourceVersionConflictException(Msg.code(961) + "Trying to delete " + theId + " but this is not the current version");
|
||||
}
|
||||
|
||||
JpaPid persistentId = JpaPid.fromId(entity.getResourceId());
|
||||
theTransactionDetails.addDeletedResourceId(persistentId);
|
||||
|
||||
// Don't delete again if it's already deleted
|
||||
if (isDeleted(entity)) {
|
||||
DaoMethodOutcome outcome = createMethodOutcomeForResourceId(entity.getIdDt().getValue(), MESSAGE_KEY_DELETE_RESOURCE_ALREADY_DELETED, StorageResponseCodeEnum.SUCCESSFUL_DELETE_ALREADY_DELETED);
|
||||
|
||||
// used to exist, so we'll set the persistent id
|
||||
outcome.setPersistentId(JpaPid.fromId(entity.getResourceId()));
|
||||
outcome.setPersistentId(persistentId);
|
||||
outcome.setEntity(entity);
|
||||
|
||||
return outcome;
|
||||
|
@ -681,7 +718,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
return myTransactionService.execute(theRequest, transactionDetails, tx -> {
|
||||
DeleteConflictList deleteConflicts = new DeleteConflictList();
|
||||
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequest);
|
||||
DeleteMethodOutcome outcome = deleteByUrl(theUrl, deleteConflicts, theRequest, transactionDetails);
|
||||
DeleteConflictUtil.validateDeleteConflictsEmptyOrThrowException(getContext(), deleteConflicts);
|
||||
return outcome;
|
||||
});
|
||||
|
@ -692,20 +729,19 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
* transaction processors
|
||||
*/
|
||||
@Override
|
||||
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequestDetails) {
|
||||
public DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails) {
|
||||
validateDeleteEnabled();
|
||||
TransactionDetails transactionDetails = new TransactionDetails();
|
||||
|
||||
return myTransactionService.execute(theRequestDetails, transactionDetails, tx -> doDeleteByUrl(theUrl, deleteConflicts, theRequestDetails));
|
||||
return myTransactionService.execute(theRequestDetails, theTransactionDetails, tx -> doDeleteByUrl(theUrl, deleteConflicts, theTransactionDetails, theRequestDetails));
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private DeleteMethodOutcome doDeleteByUrl(String theUrl, DeleteConflictList deleteConflicts, RequestDetails theRequest) {
|
||||
private DeleteMethodOutcome doDeleteByUrl(String theUrl, DeleteConflictList deleteConflicts, TransactionDetails theTransactionDetails, RequestDetails theRequestDetails) {
|
||||
ResourceSearch resourceSearch = myMatchUrlService.getResourceSearch(theUrl);
|
||||
SearchParameterMap paramMap = resourceSearch.getSearchParameterMap();
|
||||
paramMap.setLoadSynchronous(true);
|
||||
|
||||
Set<JpaPid> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequest, null);
|
||||
Set<JpaPid> resourceIds = myMatchResourceUrlService.search(paramMap, myResourceType, theRequestDetails, null);
|
||||
|
||||
if (resourceIds.size() > 1) {
|
||||
if (!getStorageSettings().isAllowMultipleDelete()) {
|
||||
|
@ -713,7 +749,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
return deletePidList(theUrl, resourceIds, deleteConflicts, theRequest);
|
||||
return deletePidList(theUrl, resourceIds, deleteConflicts, theRequestDetails, theTransactionDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -733,15 +769,23 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public <P extends IResourcePersistentId> DeleteMethodOutcome deletePidList(String theUrl, Collection<P> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
|
||||
public <P extends IResourcePersistentId> DeleteMethodOutcome deletePidList(String theUrl, Collection<P> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails) {
|
||||
StopWatch w = new StopWatch();
|
||||
TransactionDetails transactionDetails = new TransactionDetails();
|
||||
List<ResourceTable> deletedResources = new ArrayList<>();
|
||||
|
||||
List<IResourcePersistentId<?>> resolvedIds = theResourceIds
|
||||
.stream()
|
||||
.map(t -> (IResourcePersistentId<?>) t)
|
||||
.collect(Collectors.toList());
|
||||
mySystemDao.preFetchResources(resolvedIds, false);
|
||||
|
||||
for (P pid : theResourceIds) {
|
||||
JpaPid jpaPid = (JpaPid) pid;
|
||||
|
||||
// This shouldn't actually need to hit the DB because we pre-fetch above
|
||||
ResourceTable entity = myEntityManager.find(ResourceTable.class, jpaPid.getId());
|
||||
deletedResources.add(entity);
|
||||
|
||||
|
@ -750,18 +794,18 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
// Notify IServerOperationInterceptors about pre-action call
|
||||
HookParams hooks = new HookParams()
|
||||
.add(IBaseResource.class, resourceToDelete)
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||
.add(TransactionDetails.class, transactionDetails);
|
||||
doCallHooks(transactionDetails, theRequest, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
|
||||
doCallHooks(transactionDetails, theRequestDetails, Pointcut.STORAGE_PRESTORAGE_RESOURCE_DELETED, hooks);
|
||||
|
||||
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequest, transactionDetails);
|
||||
myDeleteConflictService.validateOkToDelete(theDeleteConflicts, entity, false, theRequestDetails, transactionDetails);
|
||||
|
||||
// Perform delete
|
||||
|
||||
preDelete(resourceToDelete, entity, theRequest);
|
||||
preDelete(resourceToDelete, entity, theRequestDetails);
|
||||
|
||||
updateEntityForDelete(theRequest, transactionDetails, entity);
|
||||
updateEntityForDelete(theRequestDetails, transactionDetails, entity);
|
||||
resourceToDelete.setId(entity.getIdDt());
|
||||
|
||||
// Notify JPA interceptors
|
||||
|
@ -770,11 +814,11 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
public void beforeCommit(boolean readOnly) {
|
||||
HookParams hookParams = new HookParams()
|
||||
.add(IBaseResource.class, resourceToDelete)
|
||||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(RequestDetails.class, theRequestDetails)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequestDetails)
|
||||
.add(TransactionDetails.class, transactionDetails)
|
||||
.add(InterceptorInvocationTimingEnum.class, transactionDetails.getInvocationTiming(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED));
|
||||
doCallHooks(transactionDetails, theRequest, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
|
||||
doCallHooks(transactionDetails, theRequestDetails, Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED, hookParams);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -791,6 +835,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
|
||||
ourLog.debug("Processed delete on {} (matched {} resource(s)) in {}ms", theUrl, deletedResources.size(), w.getMillis());
|
||||
|
||||
theTransactionDetails.addDeletedResourceIds(theResourceIds);
|
||||
|
||||
DeleteMethodOutcome retVal = new DeleteMethodOutcome();
|
||||
retVal.setDeletedEntities(deletedResources);
|
||||
retVal.setOperationOutcome(oo);
|
||||
|
@ -825,10 +871,10 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
boolean hasTag = false;
|
||||
for (BaseTag next : new ArrayList<>(theEntity.getTags())) {
|
||||
if (Objects.equals(next.getTag().getTagType(), nextDef.getTagType()) &&
|
||||
Objects.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
|
||||
Objects.equals(next.getTag().getCode(), nextDef.getCode()) &&
|
||||
Objects.equals(next.getTag().getVersion(), nextDef.getVersion()) &&
|
||||
Objects.equals(next.getTag().getUserSelected(), nextDef.getUserSelected())) {
|
||||
Objects.equals(next.getTag().getSystem(), nextDef.getSystem()) &&
|
||||
Objects.equals(next.getTag().getCode(), nextDef.getCode()) &&
|
||||
Objects.equals(next.getTag().getVersion(), nextDef.getVersion()) &&
|
||||
Objects.equals(next.getTag().getUserSelected(), nextDef.getUserSelected())) {
|
||||
hasTag = true;
|
||||
break;
|
||||
}
|
||||
|
@ -1367,7 +1413,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
reindexOptimizeStorageHistoryEntity(entity, historyEntity);
|
||||
if (theOptimizeStorageMode == ReindexParameters.OptimizeStorageModeEnum.ALL_VERSIONS) {
|
||||
int pageSize = 100;
|
||||
for (int page = 0; ((long)page * pageSize) < entity.getVersion(); page++) {
|
||||
for (int page = 0; ((long) page * pageSize) < entity.getVersion(); page++) {
|
||||
Slice<ResourceHistoryTable> historyEntities = myResourceHistoryTableDao.findForResourceIdAndReturnEntities(PageRequest.of(page, pageSize), entity.getId(), historyEntity.getVersion());
|
||||
for (ResourceHistoryTable next : historyEntities) {
|
||||
reindexOptimizeStorageHistoryEntity(entity, next);
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -127,7 +127,7 @@ public class PersistObservationIndexedSearchParamLastNR4IT {
|
|||
ResourceTable entity = new ResourceTable();
|
||||
entity.setId(55L);
|
||||
entity.setResourceType("Observation");
|
||||
entity.setVersion(0L);
|
||||
entity.setVersionForUnitTest(0L);
|
||||
|
||||
testObservationPersist.deleteObservationIndex(entity);
|
||||
elasticsearchSvc.refreshIndex(ElasticsearchSvcImpl.OBSERVATION_INDEX);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -153,10 +153,6 @@ public abstract class BaseHasResource extends BasePartitionable implements IBase
|
|||
myUpdated = theUpdated;
|
||||
}
|
||||
|
||||
public void setUpdated(InstantDt theUpdated) {
|
||||
myUpdated = theUpdated.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract long getVersion();
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.model.search.ResourceTableRoutingBinder;
|
|||
import ca.uhn.fhir.jpa.model.search.SearchParamTextPropertyBinder;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
import org.hibernate.Session;
|
||||
|
@ -59,6 +60,7 @@ import javax.persistence.Index;
|
|||
import javax.persistence.NamedEntityGraph;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.PostPersist;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreUpdate;
|
||||
import javax.persistence.Table;
|
||||
|
@ -67,12 +69,13 @@ import javax.persistence.Version;
|
|||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Indexed(routingBinder= @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
|
||||
@Indexed(routingBinder = @RoutingBinderRef(type = ResourceTableRoutingBinder.class))
|
||||
@Entity
|
||||
@Table(name = ResourceTable.HFJ_RESOURCE, uniqueConstraints = {}, indexes = {
|
||||
// Do not reuse previously used index name: IDX_INDEXSTATUS, IDX_RES_TYPE
|
||||
|
@ -83,23 +86,22 @@ import java.util.stream.Collectors;
|
|||
@NamedEntityGraph(name = "Resource.noJoins")
|
||||
public class ResourceTable extends BaseHasResource implements Serializable, IBasePersistedResource<JpaPid> {
|
||||
public static final int RESTYPE_LEN = 40;
|
||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final String HFJ_RESOURCE = "HFJ_RESOURCE";
|
||||
public static final String RES_TYPE = "RES_TYPE";
|
||||
|
||||
private static final int MAX_LANGUAGE_LENGTH = 20;
|
||||
private static final long serialVersionUID = 1L;
|
||||
/**
|
||||
* Holds the narrative text only - Used for Fulltext searching but not directly stored in the DB
|
||||
* Note the extra config needed in HS6 for indexing transient props:
|
||||
* https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#indexed-transient-requires-configuration
|
||||
*
|
||||
* <p>
|
||||
* Note that we depend on `myVersion` updated for this field to be indexed.
|
||||
*/
|
||||
@Transient
|
||||
@FullTextField(name = "myContentText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
|
||||
@FullTextField(name = "myContentTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteEdgeAnalyzer")
|
||||
@FullTextField(name = "myContentTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteNGramAnalyzer")
|
||||
@FullTextField(name = "myContentTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompletePhoneticAnalyzer")
|
||||
@FullTextField(name = "myContentTextEdgeNGram", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompleteEdgeAnalyzer")
|
||||
@FullTextField(name = "myContentTextNGram", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompleteNGramAnalyzer")
|
||||
@FullTextField(name = "myContentTextPhonetic", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompletePhoneticAnalyzer")
|
||||
@OptimisticLock(excluded = true)
|
||||
@IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion")))
|
||||
private String myContentText;
|
||||
|
@ -133,9 +135,9 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
*/
|
||||
@Transient()
|
||||
@FullTextField(name = "myNarrativeText", searchable = Searchable.YES, projectable = Projectable.YES, analyzer = "standardAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextEdgeNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteEdgeAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextNGram", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompleteNGramAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextPhonetic", searchable= Searchable.YES, projectable= Projectable.NO, analyzer = "autocompletePhoneticAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextEdgeNGram", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompleteEdgeAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextNGram", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompleteNGramAnalyzer")
|
||||
@FullTextField(name = "myNarrativeTextPhonetic", searchable = Searchable.YES, projectable = Projectable.NO, analyzer = "autocompletePhoneticAnalyzer")
|
||||
@OptimisticLock(excluded = true)
|
||||
@IndexingDependency(derivedFrom = @ObjectPath(@PropertyValue(propertyName = "myVersion")))
|
||||
private String myNarrativeText;
|
||||
|
@ -278,18 +280,17 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
@Transient
|
||||
private transient boolean myUnchangedInCurrentOperation;
|
||||
|
||||
|
||||
/**
|
||||
* The id of the Resource.
|
||||
* Will contain either the client-assigned id, or the sequence value.
|
||||
* Will be null during insert time until the first read.
|
||||
*
|
||||
*/
|
||||
@Column(name= "FHIR_ID",
|
||||
@Column(name = "FHIR_ID",
|
||||
// [A-Za-z0-9\-\.]{1,64} - https://www.hl7.org/fhir/datatypes.html#id
|
||||
length = 64,
|
||||
// we never update this after insert, and the Generator will otherwise "dirty" the object.
|
||||
updatable = false)
|
||||
|
||||
// inject the pk for server-assigned sequence ids.
|
||||
@GeneratorType(when = GenerationTime.INSERT, type = FhirIdGenerator.class)
|
||||
// Make sure the generator doesn't bump the history version.
|
||||
|
@ -305,30 +306,21 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
@Column(name = "SEARCH_URL_PRESENT", nullable = true)
|
||||
private Boolean mySearchUrlPresent = false;
|
||||
|
||||
/**
|
||||
* Populate myFhirId with server-assigned sequence id when no client-id provided.
|
||||
* We eat this complexity during insert to simplify query time with a uniform column.
|
||||
* Server-assigned sequence ids aren't available until just before insertion.
|
||||
* Hibernate calls insert Generators after the pk has been assigned, so we can use myId safely here.
|
||||
*/
|
||||
public static final class FhirIdGenerator implements ValueGenerator<String> {
|
||||
@Override
|
||||
public String generateValue(Session session, Object owner) {
|
||||
ResourceTable that = (ResourceTable) owner;
|
||||
return that.myFhirId != null ? that.myFhirId : that.myId.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Version
|
||||
@Column(name = "RES_VER")
|
||||
private long myVersion;
|
||||
|
||||
@OneToMany(mappedBy = "myResourceTable", fetch = FetchType.LAZY)
|
||||
private Collection<ResourceHistoryProvenanceEntity> myProvenance;
|
||||
|
||||
@Transient
|
||||
private transient ResourceHistoryTable myCurrentVersionEntity;
|
||||
|
||||
@Transient
|
||||
private transient ResourceHistoryTable myNewVersionEntity;
|
||||
|
||||
@Transient
|
||||
private transient boolean myVersionUpdatedInCurrentTransaction;
|
||||
|
||||
@OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false, mappedBy = "myResource")
|
||||
@OptimisticLock(excluded = true)
|
||||
private ForcedId myForcedId;
|
||||
|
@ -343,6 +335,39 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setting this flag is an indication that we're making changes and the version number will
|
||||
* be incremented in the current transaction. When this is set, calls to {@link #getVersion()}
|
||||
* will be incremented by one.
|
||||
* This flag is cleared in {@link #postPersist()} since at that time the new version number
|
||||
* should be reflected.
|
||||
*/
|
||||
public void markVersionUpdatedInCurrentTransaction() {
|
||||
if (!myVersionUpdatedInCurrentTransaction) {
|
||||
/*
|
||||
* Note that modifying this number doesn't actually directly affect what
|
||||
* gets stored in the database since this is a @Version field and the
|
||||
* value is therefore managed by Hibernate. So in other words, if the
|
||||
* row in the database is updated, it doesn't matter what we set
|
||||
* this field to, hibernate will increment it by one. However, we still
|
||||
* increment it for two reasons:
|
||||
* 1. The value gets used for the version attribute in the ResourceHistoryTable
|
||||
* entity we create for each new version.
|
||||
* 2. For updates to existing resources, there may actually not be any other
|
||||
* changes to this entity so incrementing this is a signal to
|
||||
* Hibernate that something changed and we need to force an entity
|
||||
* update.
|
||||
*/
|
||||
myVersion++;
|
||||
this.myVersionUpdatedInCurrentTransaction = true;
|
||||
}
|
||||
}
|
||||
|
||||
@PostPersist
|
||||
public void postPersist() {
|
||||
myVersionUpdatedInCurrentTransaction = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceTag addTag(TagDefinition theTag) {
|
||||
for (ResourceTag next : getTags()) {
|
||||
|
@ -355,7 +380,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
return tag;
|
||||
}
|
||||
|
||||
|
||||
public String getHashSha256() {
|
||||
return myHashSha256;
|
||||
}
|
||||
|
@ -558,6 +582,26 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
return myVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the version on this entity to {@literal 1}. This should only be called
|
||||
* on resources that are not yet persisted. After that time the version number
|
||||
* is managed by hibernate.
|
||||
*/
|
||||
public void initializeVersion() {
|
||||
assert myId == null;
|
||||
myVersion = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't call this in any JPA environments, the version will be ignored
|
||||
* since this field is managed by hibernate
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void setVersionForUnitTest(long theVersion) {
|
||||
myVersion = theVersion;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isDeleted() {
|
||||
return getDeleted() != null;
|
||||
|
@ -568,10 +612,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
setDeleted(null);
|
||||
}
|
||||
|
||||
public void setVersion(long theVersion) {
|
||||
myVersion = theVersion;
|
||||
}
|
||||
|
||||
public boolean isHasLinks() {
|
||||
return myHasLinks;
|
||||
}
|
||||
|
@ -689,14 +729,14 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myUnchangedInCurrentOperation = theUnchangedInCurrentOperation;
|
||||
}
|
||||
|
||||
public void setContentText(String theContentText) {
|
||||
myContentText = theContentText;
|
||||
}
|
||||
|
||||
public String getContentText() {
|
||||
return myContentText;
|
||||
}
|
||||
|
||||
public void setContentText(String theContentText) {
|
||||
myContentText = theContentText;
|
||||
}
|
||||
|
||||
public void setNarrativeText(String theNarrativeText) {
|
||||
myNarrativeText = theNarrativeText;
|
||||
}
|
||||
|
@ -709,12 +749,27 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
mySearchUrlPresent = theSearchUrlPresent;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method creates a new history entity, or might reuse the current one if we've
|
||||
* already created one in the current transaction. This is because we can only increment
|
||||
* the version once in a DB transaction (since hibernate manages that number) so creating
|
||||
* multiple {@link ResourceHistoryTable} entities will result in a constraint error.
|
||||
*/
|
||||
public ResourceHistoryTable toHistory(boolean theCreateVersionTags) {
|
||||
ResourceHistoryTable retVal = new ResourceHistoryTable();
|
||||
boolean createVersionTags = theCreateVersionTags;
|
||||
|
||||
ResourceHistoryTable retVal = myNewVersionEntity;
|
||||
if (retVal == null) {
|
||||
retVal = new ResourceHistoryTable();
|
||||
myNewVersionEntity = retVal;
|
||||
} else {
|
||||
// Tags should already be set
|
||||
createVersionTags = false;
|
||||
}
|
||||
|
||||
retVal.setResourceId(myId);
|
||||
retVal.setResourceType(myResourceType);
|
||||
retVal.setVersion(myVersion);
|
||||
retVal.setVersion(getVersion());
|
||||
retVal.setTransientForcedId(getTransientForcedId());
|
||||
|
||||
retVal.setPublished(getPublishedDate());
|
||||
|
@ -725,10 +780,8 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
retVal.setForcedId(getForcedId());
|
||||
retVal.setPartitionId(getPartitionId());
|
||||
|
||||
retVal.getTags().clear();
|
||||
|
||||
retVal.setHasTags(isHasTags());
|
||||
if (isHasTags() && theCreateVersionTags) {
|
||||
if (isHasTags() && createVersionTags) {
|
||||
for (ResourceTag next : getTags()) {
|
||||
retVal.addTag(next);
|
||||
}
|
||||
|
@ -772,16 +825,16 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
* This is a convenience to avoid loading the version a second time within a single transaction. It is
|
||||
* not persisted.
|
||||
*/
|
||||
public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
|
||||
myCurrentVersionEntity = theCurrentVersionEntity;
|
||||
public ResourceHistoryTable getCurrentVersionEntity() {
|
||||
return myCurrentVersionEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a convenience to avoid loading the version a second time within a single transaction. It is
|
||||
* not persisted.
|
||||
*/
|
||||
public ResourceHistoryTable getCurrentVersionEntity() {
|
||||
return myCurrentVersionEntity;
|
||||
public void setCurrentVersionEntity(ResourceHistoryTable theCurrentVersionEntity) {
|
||||
myCurrentVersionEntity = theCurrentVersionEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -799,8 +852,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myForcedId = theForcedId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public IdDt getIdDt() {
|
||||
IdDt retVal = new IdDt();
|
||||
|
@ -808,7 +859,6 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
public IIdType getIdType(FhirContext theContext) {
|
||||
IIdType retVal = theContext.getVersion().newIdType();
|
||||
populateId(retVal);
|
||||
|
@ -830,14 +880,14 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
}
|
||||
}
|
||||
|
||||
public void setCreatedByMatchUrl(String theCreatedByMatchUrl) {
|
||||
myCreatedByMatchUrl = theCreatedByMatchUrl;
|
||||
}
|
||||
|
||||
public String getCreatedByMatchUrl() {
|
||||
return myCreatedByMatchUrl;
|
||||
}
|
||||
|
||||
public void setCreatedByMatchUrl(String theCreatedByMatchUrl) {
|
||||
myCreatedByMatchUrl = theCreatedByMatchUrl;
|
||||
}
|
||||
|
||||
public void setLuceneIndexData(ExtendedHSearchIndexData theLuceneIndexData) {
|
||||
myLuceneIndexData = theLuceneIndexData;
|
||||
}
|
||||
|
@ -862,4 +912,18 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
public void setFhirId(String theFhirId) {
|
||||
myFhirId = theFhirId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate myFhirId with server-assigned sequence id when no client-id provided.
|
||||
* We eat this complexity during insert to simplify query time with a uniform column.
|
||||
* Server-assigned sequence ids aren't available until just before insertion.
|
||||
* Hibernate calls insert Generators after the pk has been assigned, so we can use myId safely here.
|
||||
*/
|
||||
public static final class FhirIdGenerator implements ValueGenerator<String> {
|
||||
@Override
|
||||
public String generateValue(Session session, Object owner) {
|
||||
ResourceTable that = (ResourceTable) owner;
|
||||
return that.myFhirId != null ? that.myFhirId : that.myId.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -94,7 +94,7 @@ public class SearchParamRegistryImplTest {
|
|||
ResourceTable searchParamEntity = new ResourceTable();
|
||||
searchParamEntity.setResourceType("SearchParameter");
|
||||
searchParamEntity.setId(theId);
|
||||
searchParamEntity.setVersion(theVersion);
|
||||
searchParamEntity.setVersionForUnitTest(theVersion);
|
||||
return searchParamEntity;
|
||||
}
|
||||
|
||||
|
@ -199,7 +199,7 @@ public class SearchParamRegistryImplTest {
|
|||
|
||||
// Update the resource without changing anything that would affect our cache
|
||||
ResourceTable lastEntity = newEntities.get(newEntities.size() - 1);
|
||||
lastEntity.setVersion(2);
|
||||
lastEntity.setVersionForUnitTest(2);
|
||||
resetMock(Enumerations.PublicationStatus.ACTIVE, newEntities);
|
||||
mySearchParamRegistry.requestRefresh();
|
||||
assertResult(mySearchParamRegistry.refreshCacheIfNecessary(), 0, 1, 0);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -2,7 +2,10 @@ package ca.uhn.fhir.jpa.dao.r4;
|
|||
|
||||
import ca.uhn.fhir.batch2.api.IJobDataSink;
|
||||
import ca.uhn.fhir.batch2.api.RunOutcome;
|
||||
import ca.uhn.fhir.batch2.api.VoidModel;
|
||||
import ca.uhn.fhir.batch2.jobs.chunk.ResourceIdListWorkChunkJson;
|
||||
import ca.uhn.fhir.batch2.jobs.chunk.TypedPidJson;
|
||||
import ca.uhn.fhir.batch2.jobs.expunge.DeleteExpungeStep;
|
||||
import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters;
|
||||
import ca.uhn.fhir.batch2.jobs.reindex.ReindexStep;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -10,12 +13,12 @@ import ca.uhn.fhir.context.support.ValidationSupportContext;
|
|||
import ca.uhn.fhir.context.support.ValueSetExpansionOptions;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.jpa.api.dao.ReindexParameters;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteMethodOutcome;
|
||||
import ca.uhn.fhir.jpa.api.model.HistoryCountModeEnum;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchParamPresentDao;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSet;
|
||||
import ca.uhn.fhir.jpa.entity.TermValueSetPreExpansionStatusEnum;
|
||||
import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderR4Test;
|
||||
|
@ -98,8 +101,6 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -137,26 +138,28 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
|
||||
@Autowired
|
||||
private ReindexStep myReindexStep;
|
||||
@Autowired
|
||||
private DeleteExpungeStep myDeleteExpungeStep;
|
||||
|
||||
@AfterEach
|
||||
public void afterResetDao() {
|
||||
myStorageSettings.setResourceMetaCountHardLimit(new JpaStorageSettings().getResourceMetaCountHardLimit());
|
||||
myStorageSettings.setIndexMissingFields(new JpaStorageSettings().getIndexMissingFields());
|
||||
myStorageSettings.setDeleteEnabled(new JpaStorageSettings().isDeleteEnabled());
|
||||
myStorageSettings.setMatchUrlCacheEnabled(new JpaStorageSettings().isMatchUrlCacheEnabled());
|
||||
myStorageSettings.setHistoryCountMode(JpaStorageSettings.DEFAULT_HISTORY_COUNT_MODE);
|
||||
myStorageSettings.setMassIngestionMode(new JpaStorageSettings().isMassIngestionMode());
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths(new JpaStorageSettings().getAutoVersionReferenceAtPaths());
|
||||
myStorageSettings.setRespectVersionsForSearchIncludes(new JpaStorageSettings().isRespectVersionsForSearchIncludes());
|
||||
myFhirContext.getParserOptions().setStripVersionsFromReferences(true);
|
||||
myStorageSettings.setTagStorageMode(new JpaStorageSettings().getTagStorageMode());
|
||||
myStorageSettings.clearSupportedSubscriptionTypesForUnitTest();
|
||||
myStorageSettings.setAllowMultipleDelete(new JpaStorageSettings().isAllowMultipleDelete());
|
||||
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(new JpaStorageSettings().isAutoCreatePlaceholderReferenceTargets());
|
||||
myStorageSettings.setAutoVersionReferenceAtPaths(new JpaStorageSettings().getAutoVersionReferenceAtPaths());
|
||||
myStorageSettings.setDeleteEnabled(new JpaStorageSettings().isDeleteEnabled());
|
||||
myStorageSettings.setHistoryCountMode(JpaStorageSettings.DEFAULT_HISTORY_COUNT_MODE);
|
||||
myStorageSettings.setIndexMissingFields(new JpaStorageSettings().getIndexMissingFields());
|
||||
myStorageSettings.setInlineResourceTextBelowSize(new JpaStorageSettings().getInlineResourceTextBelowSize());
|
||||
myStorageSettings.setMassIngestionMode(new JpaStorageSettings().isMassIngestionMode());
|
||||
myStorageSettings.setMatchUrlCacheEnabled(new JpaStorageSettings().isMatchUrlCacheEnabled());
|
||||
myStorageSettings.setPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets(new JpaStorageSettings().isPopulateIdentifierInAutoCreatedPlaceholderReferenceTargets());
|
||||
myStorageSettings.setResourceClientIdStrategy(new JpaStorageSettings().getResourceClientIdStrategy());
|
||||
myStorageSettings.setResourceMetaCountHardLimit(new JpaStorageSettings().getResourceMetaCountHardLimit());
|
||||
myStorageSettings.setRespectVersionsForSearchIncludes(new JpaStorageSettings().isRespectVersionsForSearchIncludes());
|
||||
myStorageSettings.setTagStorageMode(new JpaStorageSettings().getTagStorageMode());
|
||||
myStorageSettings.setInlineResourceTextBelowSize(new JpaStorageSettings().getInlineResourceTextBelowSize());
|
||||
myStorageSettings.clearSupportedSubscriptionTypesForUnitTest();
|
||||
|
||||
myFhirContext.getParserOptions().setStripVersionsFromReferences(true);
|
||||
TermReadSvcImpl.setForceDisableHibernateSearchForUnitTest(false);
|
||||
}
|
||||
|
||||
|
@ -418,7 +421,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -452,7 +454,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -478,7 +479,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -533,7 +533,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(0, myCaptureQueriesListener.getCommitCount());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -559,7 +558,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -650,7 +648,6 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -740,6 +737,62 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(0, myCaptureQueriesListener.getDeleteQueriesForCurrentThread().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteMultiple() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
createPatient(withId("PT" + i), withActiveTrue(), withIdentifier("http://foo", "id" + i), withFamily("Family" + i));
|
||||
}
|
||||
|
||||
myStorageSettings.setAllowMultipleDelete(true);
|
||||
|
||||
// Test
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
DeleteMethodOutcome outcome = myPatientDao.deleteByUrl("Patient?active=true", new SystemRequestDetails());
|
||||
|
||||
// Validate
|
||||
assertEquals(13, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(10, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(10, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(30, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
assertEquals(10, outcome.getDeletedEntities().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteExpungeStep() {
|
||||
// Setup
|
||||
for (int i = 0; i < 10; i++) {
|
||||
createPatient(
|
||||
withId("PT" + i),
|
||||
withActiveTrue(),
|
||||
withIdentifier("http://foo", "id" + i),
|
||||
withFamily("Family" + i),
|
||||
withTag("http://foo", "blah"));
|
||||
}
|
||||
List<TypedPidJson> pids = runInTransaction(() -> myForcedIdDao
|
||||
.findAll()
|
||||
.stream()
|
||||
.map(t -> new TypedPidJson(t.getResourceType(), Long.toString(t.getResourceId())))
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
runInTransaction(()-> assertEquals(10, myResourceTableDao.count()));
|
||||
|
||||
IJobDataSink<VoidModel> sink = mock(IJobDataSink.class);
|
||||
|
||||
// Test
|
||||
myCaptureQueriesListener.clear();
|
||||
RunOutcome outcome = myDeleteExpungeStep.doDeleteExpunge(new ResourceIdListWorkChunkJson(pids), sink, "instance-id", "chunk-id");
|
||||
|
||||
// Verify
|
||||
assertEquals(1, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(29, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
assertEquals(10, outcome.getRecordsProcessed());
|
||||
runInTransaction(()-> assertEquals(0, myResourceTableDao.count()));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
|
@ -3185,6 +3238,8 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
return (Bundle) bb.getBundle();
|
||||
};
|
||||
|
||||
// Pass 1
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
assertEquals(2, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
|
@ -3192,13 +3247,55 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
// Pass 2
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
myCaptureQueriesListener.logSelectQueriesForCurrentThread();
|
||||
Bundle outcome = mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
assertEquals(8, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(7, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
IdType patientId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("2", patientId.getVersionIdPart());
|
||||
|
||||
Patient patient = myPatientDao.read(patientId, mySrd);
|
||||
assertEquals(1, patient.getMeta().getProfile().size());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("SMITH", patient.getNameFirstRep().getFamily());
|
||||
patient = myPatientDao.read(patientId.withVersion("1"), mySrd);
|
||||
assertEquals(1, patient.getMeta().getProfile().size());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("SMITH", patient.getNameFirstRep().getFamily());
|
||||
|
||||
// Pass 3
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(new SystemRequestDetails(), supplier.get());
|
||||
assertEquals(8, myCaptureQueriesListener.countSelectQueriesForCurrentThread());
|
||||
myCaptureQueriesListener.logInsertQueries();
|
||||
assertEquals(4, myCaptureQueriesListener.countInsertQueriesForCurrentThread());
|
||||
assertEquals(6, myCaptureQueriesListener.countUpdateQueriesForCurrentThread());
|
||||
assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread());
|
||||
|
||||
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
patientId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("3", patientId.getVersionIdPart());
|
||||
|
||||
patient = myPatientDao.read(patientId, mySrd);
|
||||
assertEquals(1, patient.getMeta().getProfile().size());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("SMITH", patient.getNameFirstRep().getFamily());
|
||||
patient = myPatientDao.read(patientId.withVersion("2"), mySrd);
|
||||
assertEquals(1, patient.getMeta().getProfile().size());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("SMITH", patient.getNameFirstRep().getFamily());
|
||||
patient = myPatientDao.read(patientId.withVersion("1"), mySrd);
|
||||
assertEquals(1, patient.getMeta().getProfile().size());
|
||||
assertEquals("http://foo", patient.getMeta().getProfile().get(0).getValue());
|
||||
assertEquals("SMITH", patient.getNameFirstRep().getFamily());
|
||||
}
|
||||
|
||||
|
||||
|
@ -3206,7 +3303,7 @@ public class FhirResourceDaoR4QueryCountTest extends BaseResourceProviderR4Test
|
|||
* See the class javadoc before changing the counts in this test!
|
||||
*/
|
||||
@Test
|
||||
public void testMassIngestionMode_TransactionWithChanges_2() throws IOException {
|
||||
public void testMassIngestionMode_TransactionWithChanges_NonVersionedTags() throws IOException {
|
||||
myStorageSettings.setDeleteEnabled(false);
|
||||
myStorageSettings.setMatchUrlCacheEnabled(true);
|
||||
myStorageSettings.setMassIngestionMode(true);
|
||||
|
|
|
@ -100,6 +100,9 @@ public class FhirResourceDaoR4TagsInlineTest extends BaseResourceProviderR4Test
|
|||
mySearchParameterDao.update(searchParameter, mySrd);
|
||||
mySearchParamRegistry.forceRefresh();
|
||||
|
||||
logAllResources();
|
||||
logAllResourceVersions();
|
||||
|
||||
createPatientsForInlineSearchTests();
|
||||
|
||||
logAllTokenIndexes();
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.delete.job;
|
|||
import ca.uhn.fhir.batch2.api.IJobCoordinator;
|
||||
import ca.uhn.fhir.batch2.jobs.expunge.DeleteExpungeAppCtx;
|
||||
import ca.uhn.fhir.batch2.jobs.expunge.DeleteExpungeJobParameters;
|
||||
import ca.uhn.fhir.batch2.model.JobInstance;
|
||||
import ca.uhn.fhir.batch2.model.JobInstanceStartRequest;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.batch.models.Batch2JobStartResponse;
|
||||
|
|
|
@ -254,7 +254,7 @@ public class ReindexJobTest extends BaseJpaR4Test {
|
|||
resource.setDeleted(currentDate);
|
||||
resource.setUpdated(currentDate);
|
||||
resource.setHashSha256(null);
|
||||
resource.setVersion(2L);
|
||||
resource.setVersionForUnitTest(2L);
|
||||
myResourceTableDao.save(resource);
|
||||
});
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.dao.r5;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.util.BundleBuilder;
|
||||
import org.hl7.fhir.r5.model.BooleanType;
|
||||
import org.hl7.fhir.r5.model.Bundle;
|
||||
|
@ -12,15 +14,22 @@ import org.hl7.fhir.r5.model.Patient;
|
|||
import org.hl7.fhir.r5.model.Quantity;
|
||||
import org.hl7.fhir.r5.model.Reference;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.countMatches;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
||||
|
||||
|
@ -30,6 +39,7 @@ public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
|||
myStorageSettings.setIndexMissingFields(defaults.getIndexMissingFields());
|
||||
myStorageSettings.setMatchUrlCacheEnabled(defaults.isMatchUrlCacheEnabled());
|
||||
myStorageSettings.setDeleteEnabled(defaults.isDeleteEnabled());
|
||||
myStorageSettings.setInlineResourceTextBelowSize(defaults.getInlineResourceTextBelowSize());
|
||||
}
|
||||
|
||||
|
||||
|
@ -435,6 +445,248 @@ public class FhirSystemDaoTransactionR5Test extends BaseJpaR5Test {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a conditional delete and conditional update are both used on the same condition,
|
||||
* the update should win.
|
||||
*/
|
||||
@Test
|
||||
public void testConditionalDeleteAndConditionalUpdateOnSameResource() {
|
||||
Bundle outcome;
|
||||
Patient actual;
|
||||
|
||||
// First pass (resource doesn't already exist)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalUpdateOnSameResource(myFhirContext));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/1"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
IdType resourceId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("1", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Second pass (resource already exists)
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalUpdateOnSameResource(myFhirContext));
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/2"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
logAllResources();
|
||||
logAllResourceVersions();
|
||||
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("2", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Third pass (resource already exists)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalUpdateOnSameResource(myFhirContext));
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/3"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("3", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConditionalDeleteAndConditionalUpdateOnSameResource_MultipleMatchesAlreadyExist() {
|
||||
|
||||
// Setup
|
||||
|
||||
myPatientDao.create(createPatientWithIdentifierAndTag(), mySrd);
|
||||
myPatientDao.create(createPatientWithIdentifierAndTag(), mySrd);
|
||||
|
||||
// Test
|
||||
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalUpdateOnSameResource(myFhirContext));
|
||||
fail();
|
||||
} catch (PreconditionFailedException e) {
|
||||
|
||||
// Verify
|
||||
assertThat(e.getMessage(), containsString("Multiple resources match this search"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a conditional delete and conditional update are both used on the same condition,
|
||||
* the update should win.
|
||||
*/
|
||||
@Test
|
||||
public void testConditionalDeleteAndConditionalCreateOnSameResource() {
|
||||
Bundle outcome;
|
||||
Patient actual;
|
||||
|
||||
// First pass (resource doesn't already exist)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalCreateOnSameResource(myFhirContext));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/1"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
IdType resourceId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("1", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Second pass (resource already exists)
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalCreateOnSameResource(myFhirContext));
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/1"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
logAllResources();
|
||||
logAllResourceVersions();
|
||||
|
||||
IdType resourceId2 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
|
||||
assertNotEquals(resourceId.getIdPart(), resourceId2.getIdPart());
|
||||
actual = myPatientDao.read(resourceId2, mySrd);
|
||||
assertEquals("1", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Third pass (resource already exists)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithConditionalDeleteAndConditionalCreateOnSameResource(myFhirContext));
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertThat(outcome.getEntry().get(1).getResponse().getLocation(), endsWith("_history/1"));
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
IdType resourceId3 = new IdType(outcome.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
|
||||
assertNotEquals(resourceId2.getIdPart(), resourceId3.getIdPart());
|
||||
actual = myPatientDao.read(resourceId3, mySrd);
|
||||
assertEquals("1", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
}
|
||||
|
||||
/**
|
||||
* There's not much point to deleting and updating the same resource in a
|
||||
* transaction, but let's make sure we at least don't end up in a bad state
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteAndUpdateOnSameResource() {
|
||||
|
||||
Bundle outcome;
|
||||
Patient actual;
|
||||
|
||||
// First pass (resource doesn't already exist)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithDeleteAndUpdateOnSameResource(myFhirContext));
|
||||
assertEquals(null, outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("Patient/P/_history/1", outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
IdType resourceId = new IdType(outcome.getEntry().get(1).getResponse().getLocation()).toUnqualifiedVersionless();
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("1", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Second pass (resource already exists)
|
||||
|
||||
myCaptureQueriesListener.clear();
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithDeleteAndUpdateOnSameResource(myFhirContext));
|
||||
myCaptureQueriesListener.logUpdateQueries();
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals("Patient/P/_history/2", outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("Patient/P/_history/2", outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
logAllResources();
|
||||
logAllResourceVersions();
|
||||
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("2", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
// Third pass (resource already exists)
|
||||
|
||||
outcome = mySystemDao.transaction(mySrd, createBundleWithDeleteAndUpdateOnSameResource(myFhirContext));
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
|
||||
assertEquals("Patient/P/_history/3", outcome.getEntry().get(0).getResponse().getLocation());
|
||||
assertEquals("204 No Content", outcome.getEntry().get(0).getResponse().getStatus());
|
||||
assertEquals("Patient/P/_history/3", outcome.getEntry().get(1).getResponse().getLocation());
|
||||
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
|
||||
|
||||
actual = myPatientDao.read(resourceId, mySrd);
|
||||
assertEquals("3", actual.getIdElement().getVersionIdPart());
|
||||
assertEquals("http://foo", actual.getIdentifierFirstRep().getSystem());
|
||||
assertEquals("http://tag", actual.getMeta().getTagFirstRep().getSystem());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Nonnull
|
||||
private static Bundle createBundleWithConditionalDeleteAndConditionalUpdateOnSameResource(FhirContext theFhirContext) {
|
||||
// Build a new bundle each time we need it
|
||||
BundleBuilder bb = new BundleBuilder(theFhirContext);
|
||||
bb.addTransactionDeleteConditionalEntry("Patient?identifier=http://foo|bar");
|
||||
|
||||
Patient patient = createPatientWithIdentifierAndTag();
|
||||
bb.addTransactionUpdateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
||||
return bb.getBundleTyped();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Bundle createBundleWithConditionalDeleteAndConditionalCreateOnSameResource(FhirContext theFhirContext) {
|
||||
// Build a new bundle each time we need it
|
||||
BundleBuilder bb = new BundleBuilder(theFhirContext);
|
||||
bb.addTransactionDeleteConditionalEntry("Patient?identifier=http://foo|bar");
|
||||
|
||||
Patient patient = createPatientWithIdentifierAndTag();
|
||||
patient.setId((String)null);
|
||||
bb.addTransactionCreateEntry(patient).conditional("Patient?identifier=http://foo|bar");
|
||||
return bb.getBundleTyped();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Bundle createBundleWithDeleteAndUpdateOnSameResource(FhirContext theFhirContext) {
|
||||
// Build a new bundle each time we need it
|
||||
BundleBuilder bb = new BundleBuilder(theFhirContext);
|
||||
bb.addTransactionDeleteEntry(new IdType("Patient/P"));
|
||||
bb.addTransactionUpdateEntry(createPatientWithIdentifierAndTag());
|
||||
return bb.getBundleTyped();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private static Patient createPatientWithIdentifierAndTag() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("Patient/P");
|
||||
patient.getMeta().addTag("http://tag", "tag-code", "tag-display");
|
||||
patient.addIdentifier().setSystem("http://foo").setValue("bar");
|
||||
return patient;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -33,13 +33,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
|
@ -62,6 +56,7 @@ public class TransactionDetails {
|
|||
private Map<String, IResourcePersistentId> myResolvedResourceIds = Collections.emptyMap();
|
||||
private Map<String, IResourcePersistentId> myResolvedMatchUrls = Collections.emptyMap();
|
||||
private Map<String, Supplier<IBaseResource>> myResolvedResources = Collections.emptyMap();
|
||||
private Set<IResourcePersistentId> myDeletedResourceIds = Collections.emptySet();
|
||||
private Map<String, Object> myUserData;
|
||||
private ListMultimap<Pointcut, HookParams> myDeferredInterceptorBroadcasts;
|
||||
private EnumSet<Pointcut> myDeferredInterceptorBroadcastPointcuts;
|
||||
|
@ -114,6 +109,34 @@ public class TransactionDetails {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public void addDeletedResourceId(@Nonnull IResourcePersistentId theResourceId) {
|
||||
Validate.notNull(theResourceId, "theResourceId must not be null");
|
||||
if (myDeletedResourceIds.isEmpty()) {
|
||||
myDeletedResourceIds = new HashSet<>();
|
||||
}
|
||||
myDeletedResourceIds.add(theResourceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.8.0
|
||||
*/
|
||||
public void addDeletedResourceIds(Collection<? extends IResourcePersistentId> theResourceIds) {
|
||||
for (IResourcePersistentId<?> next : theResourceIds) {
|
||||
addDeletedResourceId(next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.8.0
|
||||
*/
|
||||
@Nonnull
|
||||
public Set<IResourcePersistentId> getDeletedResourceIds() {
|
||||
return Collections.unmodifiableSet(myDeletedResourceIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* A <b>Resolved Resource ID</b> is a mapping between a resource ID (e.g. "<code>Patient/ABC</code>" or
|
||||
* "<code>Observation/123</code>") and a storage ID for that resource. Resources should only be placed within
|
||||
|
@ -223,6 +246,15 @@ public class TransactionDetails {
|
|||
myResolvedMatchUrls.put(theConditionalUrl, thePersistentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.8.0
|
||||
* @see #addResolvedMatchUrl(FhirContext, String, IResourcePersistentId)
|
||||
*/
|
||||
public void removeResolvedMatchUrl(String theMatchUrl) {
|
||||
myResolvedMatchUrls.remove(theMatchUrl);
|
||||
}
|
||||
|
||||
|
||||
private boolean matchUrlWithDiffIdExists(String theConditionalUrl, @Nonnull IResourcePersistentId thePersistentId) {
|
||||
if (myResolvedMatchUrls.containsKey(theConditionalUrl) && myResolvedMatchUrls.get(theConditionalUrl) != NOT_FOUND) {
|
||||
return myResolvedMatchUrls.get(theConditionalUrl).getId() != thePersistentId.getId();
|
||||
|
@ -359,13 +391,13 @@ public class TransactionDetails {
|
|||
return !myResolvedResourceIds.isEmpty();
|
||||
}
|
||||
|
||||
public void setFhirTransaction(boolean theFhirTransaction) {
|
||||
myFhirTransaction = theFhirTransaction;
|
||||
}
|
||||
|
||||
public boolean isFhirTransaction() {
|
||||
return myFhirTransaction;
|
||||
}
|
||||
|
||||
public void setFhirTransaction(boolean theFhirTransaction) {
|
||||
myFhirTransaction = theFhirTransaction;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
@ -21,7 +21,7 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-caching-api</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -96,7 +96,7 @@ public class DeleteExpungeStep implements IJobStepWorker<ReindexJobParameters, R
|
|||
List<JpaPid> persistentIds = myData.getResourcePersistentIds(myIdHelperService);
|
||||
|
||||
if (persistentIds.isEmpty()) {
|
||||
ourLog.info("Starting delete expunge work chunk. Ther are no resources to delete expunge - Instance[{}] Chunk[{}]", myInstanceId, myChunkId);
|
||||
ourLog.info("Starting delete expunge work chunk. There are no resources to delete expunge - Instance[{}] Chunk[{}]", myInstanceId, myChunkId);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -85,7 +85,7 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
* won't be indexed and searches won't work.
|
||||
* @param theRequestDetails The request details including permissions and partitioning information
|
||||
*/
|
||||
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, @Nonnull TransactionDetails theTransactionDetails, RequestDetails theRequestDetails);
|
||||
DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
|
||||
|
||||
DaoMethodOutcome create(T theResource, String theIfNoneExist, RequestDetails theRequestDetails);
|
||||
|
||||
|
@ -111,14 +111,40 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
/**
|
||||
* This method does not throw an exception if there are delete conflicts, but populates them
|
||||
* in the provided list
|
||||
*
|
||||
* @since 6.8.0
|
||||
*/
|
||||
DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails);
|
||||
DeleteMethodOutcome deleteByUrl(String theUrl, DeleteConflictList theDeleteConflictsListToPopulate, RequestDetails theRequestDetails, @Nonnull TransactionDetails theTransactionDetails);
|
||||
|
||||
/**
|
||||
* This method throws an exception if there are delete conflicts
|
||||
*/
|
||||
DeleteMethodOutcome deleteByUrl(String theString, RequestDetails theRequestDetails);
|
||||
|
||||
/**
|
||||
* @deprecated Deprecated in 6.8.0 - Use and implement {@link #deletePidList(String, Collection, DeleteConflictList, RequestDetails, TransactionDetails)}
|
||||
*/
|
||||
default <P extends IResourcePersistentId> DeleteMethodOutcome deletePidList(String theUrl, Collection<P> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest) {
|
||||
return deletePidList(theUrl, theResourceIds, theDeleteConflicts, theRequest, new TransactionDetails());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a list of resource Pids
|
||||
* <p>
|
||||
* CAUTION: This list does not throw an exception if there are delete conflicts. It should always be followed by
|
||||
* a call to DeleteConflictUtil.validateDeleteConflictsEmptyOrThrowException(fhirContext, conflicts);
|
||||
* to actually throw the exception. The reason this method doesn't do that itself is that it is expected to be
|
||||
* called repeatedly where an earlier conflict can be removed in a subsequent pass.
|
||||
*
|
||||
* @param theUrl the original URL that triggered the deletion
|
||||
* @param theResourceIds the ids of the resources to be deleted
|
||||
* @param theDeleteConflicts out parameter of conflicts preventing deletion
|
||||
* @param theRequestDetails the request that initiated the request
|
||||
* @return response back to the client
|
||||
* @since 6.8.0
|
||||
*/
|
||||
<P extends IResourcePersistentId> DeleteMethodOutcome deletePidList(String theUrl, Collection<P> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequestDetails, TransactionDetails theTransactionDetails);
|
||||
|
||||
ExpungeOutcome expunge(ExpungeOptions theExpungeOptions, RequestDetails theRequestDetails);
|
||||
|
||||
ExpungeOutcome expunge(IIdType theIIdType, ExpungeOptions theExpungeOptions, RequestDetails theRequest);
|
||||
|
@ -331,22 +357,6 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
|
||||
RuntimeResourceDefinition validateCriteriaAndReturnResourceDefinition(String criteria);
|
||||
|
||||
/**
|
||||
* Delete a list of resource Pids
|
||||
* <p>
|
||||
* CAUTION: This list does not throw an exception if there are delete conflicts. It should always be followed by
|
||||
* a call to DeleteConflictUtil.validateDeleteConflictsEmptyOrThrowException(fhirContext, conflicts);
|
||||
* to actually throw the exception. The reason this method doesn't do that itself is that it is expected to be
|
||||
* called repeatedly where an earlier conflict can be removed in a subsequent pass.
|
||||
*
|
||||
* @param theUrl the original URL that triggered the delete
|
||||
* @param theResourceIds the ids of the resources to be deleted
|
||||
* @param theDeleteConflicts out parameter of conflicts preventing deletion
|
||||
* @param theRequest the request that initiated the request
|
||||
* @return response back to the client
|
||||
*/
|
||||
<P extends IResourcePersistentId> DeleteMethodOutcome deletePidList(String theUrl, Collection<P> theResourceIds, DeleteConflictList theDeleteConflicts, RequestDetails theRequest);
|
||||
|
||||
/**
|
||||
* @deprecated use #read(IIdType, RequestDetails) instead
|
||||
*/
|
||||
|
|
|
@ -961,7 +961,7 @@ public abstract class BaseTransactionProcessor {
|
|||
String matchUrl = myVersionAdapter.getEntryRequestIfNoneExist(nextReqEntry);
|
||||
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||
// create individual resource
|
||||
outcome = resourceDao.create(res, matchUrl, false, theTransactionDetails, theRequest);
|
||||
outcome = resourceDao.create(res, matchUrl, false, theRequest, theTransactionDetails);
|
||||
setConditionalUrlToBeValidatedLater(conditionalUrlToIdMap, matchUrl, outcome.getId());
|
||||
res.setId(outcome.getId());
|
||||
|
||||
|
@ -999,7 +999,7 @@ public abstract class BaseTransactionProcessor {
|
|||
} else {
|
||||
String matchUrl = parts.getResourceType() + '?' + parts.getParams();
|
||||
matchUrl = performIdSubstitutionsInMatchUrl(theIdSubstitutions, matchUrl);
|
||||
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequest);
|
||||
DeleteMethodOutcome deleteOutcome = dao.deleteByUrl(matchUrl, deleteConflicts, theRequest, theTransactionDetails);
|
||||
setConditionalUrlToBeValidatedLater(conditionalUrlToIdMap, matchUrl, deleteOutcome.getId());
|
||||
List<? extends IBasePersistedResource> allDeleted = deleteOutcome.getDeletedEntities();
|
||||
for (IBasePersistedResource deleted : allDeleted) {
|
||||
|
|
|
@ -222,4 +222,9 @@ public class MatchResourceUrlService<T extends IResourcePersistentId> {
|
|||
}
|
||||
}
|
||||
|
||||
public void unresolveMatchUrl(TransactionDetails theTransactionDetails, String theResourceType, String theMatchUrl) {
|
||||
Validate.notBlank(theMatchUrl);
|
||||
String matchUrl = massageForStorage(theResourceType, theMatchUrl);
|
||||
theTransactionDetails.removeResolvedMatchUrl(matchUrl);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import org.hl7.fhir.r4.model.AuditEvent;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import org.hl7.fhir.r4.model.AuditEvent;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Storage api
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.storage.interceptor.balp;
|
||||
|
||||
import org.hl7.fhir.r4.model.AuditEvent;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.parser;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.parser;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.fhir.parser;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.test.concurrency;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR Test Utilities
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2023 Smile CDR, Inc.
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
package ca.uhn.test.concurrency;
|
||||
|
||||
import ca.uhn.fhir.i18n.Msg;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>6.7.0-SNAPSHOT</version>
|
||||
<version>6.7.1-SNAPSHOT</version>
|
||||
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue