5290 - Feature Request - Implement toggle to prevent conditional updates from invalidating match criteria. (#5291)
* Test skeleton, TODO IMPLEMENTER added all over the place * Add test and TODO IMPLEMENTER * Add jpa storage property to allow preventing conditional updates to invalidate match criteria * spotless * Implement review suggestions --------- Co-authored-by: juan.marchionatto <juan.marchionatto@smilecdr.com>
This commit is contained in:
parent
67e421649b
commit
49a28efd53
|
@ -36,6 +36,8 @@ public final class HapiSystemProperties {
|
|||
static final String TEST_MODE = "test";
|
||||
static final String UNIT_TEST_MODE = "unit_test_mode";
|
||||
static final long DEFAULT_TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS = 10 * DateUtils.MILLIS_PER_SECOND;
|
||||
static final String PREVENT_INVALIDATING_CONDITIONAL_MATCH_CRITERIA =
|
||||
"hapi.storage.prevent_invalidating_conditional_match_criteria";
|
||||
|
||||
private HapiSystemProperties() {}
|
||||
|
||||
|
@ -158,4 +160,9 @@ public final class HapiSystemProperties {
|
|||
public static boolean isSuppressHapiFhirVersionLogEnabled() {
|
||||
return Boolean.parseBoolean(System.getProperty(SUPPRESS_HAPI_FHIR_VERSION_LOG));
|
||||
}
|
||||
|
||||
public static boolean isPreventInvalidatingConditionalMatchCriteria() {
|
||||
return Boolean.parseBoolean(System.getProperty(
|
||||
HapiSystemProperties.PREVENT_INVALIDATING_CONDITIONAL_MATCH_CRITERIA, Boolean.FALSE.toString()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
type: add
|
||||
issue: 5290
|
||||
title: "Added storage property to prevent conditional updates from invalidating match criteria."
|
|
@ -179,3 +179,10 @@ Clients may want to disable this setting for performance reasons as it populates
|
|||
|
||||
Setting this property explicitly to false disables the feature: [Non Resource DB History](/apidocs/hapi-fhir-storage/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.html#isNonResourceDbHistoryEnabled())
|
||||
|
||||
# Prevent Conditional Updates to Invalidate Match Criteria
|
||||
|
||||
JPA Server prevents conditional updated to invalidate match criteria for first version of resources.
|
||||
This setting, disabled by default, allows to configure the same behaviour for later versions.
|
||||
|
||||
Setting this property explicitly to true enables the feature: [Prevent Conditional Updates Invalidating Match Criteria](/apidocs/hapi-fhir-storage/ca/uhn/fhir/jpa/api/config/JpaStorageSettings.html#isPreventInvalidatingConditionalMatchCriteria())
|
||||
|
||||
|
|
|
@ -1036,7 +1036,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
myDaoSearchParamSynchronizer = theDaoSearchParamSynchronizer;
|
||||
}
|
||||
|
||||
private void verifyMatchUrlForConditionalCreate(
|
||||
private void verifyMatchUrlForConditionalCreateOrUpdate(
|
||||
CreateOrUpdateByMatch theCreateOrUpdate,
|
||||
IBaseResource theResource,
|
||||
String theIfNoneExist,
|
||||
ResourceIndexedSearchParams theParams,
|
||||
|
@ -1044,13 +1045,19 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// Make sure that the match URL was actually appropriate for the supplied resource
|
||||
InMemoryMatchResult outcome =
|
||||
myInMemoryResourceMatcher.match(theIfNoneExist, theResource, theParams, theRequestDetails);
|
||||
|
||||
if (outcome.supported() && !outcome.matched()) {
|
||||
throw new InvalidRequestException(
|
||||
Msg.code(929)
|
||||
+ "Failed to process conditional create. The supplied resource did not satisfy the conditional URL.");
|
||||
String errorMsg = getConditionalCreateOrUpdateErrorMsg(theCreateOrUpdate);
|
||||
throw new InvalidRequestException(Msg.code(929) + errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private String getConditionalCreateOrUpdateErrorMsg(CreateOrUpdateByMatch theCreateOrUpdate) {
|
||||
return String.format(
|
||||
"Failed to process conditional %s. " + "The supplied resource did not satisfy the conditional URL.",
|
||||
theCreateOrUpdate.name().toLowerCase());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public ResourceTable updateEntity(
|
||||
|
@ -1173,17 +1180,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
}
|
||||
|
||||
if (changed.isChanged()) {
|
||||
|
||||
// Make sure that the match URL was actually appropriate for the supplied
|
||||
// resource. We only do this for version 1 right now since technically it
|
||||
// is possible (and legal) for someone to be using a conditional update
|
||||
// to match a resource and then update it in a way that it no longer
|
||||
// matches. We could certainly make this configurable though in the
|
||||
// future.
|
||||
if (entity.getVersion() <= 1L && entity.getCreatedByMatchUrl() != null && thePerformIndexing) {
|
||||
verifyMatchUrlForConditionalCreate(
|
||||
theResource, entity.getCreatedByMatchUrl(), newParams, theRequest);
|
||||
}
|
||||
checkConditionalMatch(
|
||||
entity, theUpdateVersion, theResource, thePerformIndexing, newParams, theRequest);
|
||||
|
||||
if (CURRENTLY_REINDEXING.get(theResource) != Boolean.TRUE) {
|
||||
entity.setUpdated(theTransactionDetails.getTransactionDate());
|
||||
|
@ -1333,6 +1331,52 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the match URL was actually appropriate for the supplied
|
||||
* resource, if so configured, or do it only for first version, since technically it
|
||||
* is possible (and legal) for someone to be using a conditional update
|
||||
* to match a resource and then update it in a way that it no longer
|
||||
* matches.
|
||||
*/
|
||||
private void checkConditionalMatch(
|
||||
ResourceTable theEntity,
|
||||
boolean theUpdateVersion,
|
||||
IBaseResource theResource,
|
||||
boolean thePerformIndexing,
|
||||
ResourceIndexedSearchParams theNewParams,
|
||||
RequestDetails theRequest) {
|
||||
|
||||
if (!thePerformIndexing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (theEntity.getCreatedByMatchUrl() == null && theEntity.getUpdatedByMatchUrl() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// version is not updated at this point, but could be pending for update, which we consider here
|
||||
long pendingVersion = theEntity.getVersion();
|
||||
if (theUpdateVersion && !theEntity.isVersionUpdatedInCurrentTransaction()) {
|
||||
pendingVersion++;
|
||||
}
|
||||
|
||||
if (myStorageSettings.isPreventInvalidatingConditionalMatchCriteria() || pendingVersion <= 1L) {
|
||||
String createOrUpdateUrl;
|
||||
CreateOrUpdateByMatch createOrUpdate;
|
||||
|
||||
if (theEntity.getCreatedByMatchUrl() != null) {
|
||||
createOrUpdateUrl = theEntity.getCreatedByMatchUrl();
|
||||
createOrUpdate = CreateOrUpdateByMatch.CREATE;
|
||||
} else {
|
||||
createOrUpdateUrl = theEntity.getUpdatedByMatchUrl();
|
||||
createOrUpdate = CreateOrUpdateByMatch.UPDATE;
|
||||
}
|
||||
|
||||
verifyMatchUrlForConditionalCreateOrUpdate(
|
||||
createOrUpdate, theResource, createOrUpdateUrl, theNewParams, theRequest);
|
||||
}
|
||||
}
|
||||
|
||||
public IBasePersistedResource updateHistoryEntity(
|
||||
RequestDetails theRequest,
|
||||
T theResource,
|
||||
|
@ -1609,6 +1653,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
// Notify IServerOperationInterceptors about pre-action call
|
||||
notifyInterceptors(theRequestDetails, theResource, theOldResource, theTransactionDetails, true);
|
||||
|
||||
entity.setUpdatedByMatchUrl(theMatchUrl);
|
||||
|
||||
// Perform update
|
||||
ResourceTable savedEntity = updateEntity(
|
||||
theRequestDetails,
|
||||
|
@ -1990,4 +2036,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
public static void setValidationDisabledForUnitTest(boolean theValidationDisabledForUnitTest) {
|
||||
ourValidationDisabledForUnitTest = theValidationDisabledForUnitTest;
|
||||
}
|
||||
|
||||
private enum CreateOrUpdateByMatch {
|
||||
CREATE,
|
||||
UPDATE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2212,6 +2212,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
IIdType resourceId;
|
||||
RestOperationTypeEnum update = RestOperationTypeEnum.UPDATE;
|
||||
if (isNotBlank(theMatchUrl)) {
|
||||
// Validate that the supplied resource matches the conditional.
|
||||
Set<JpaPid> match = myMatchResourceUrlService.processMatchUrl(
|
||||
theMatchUrl, myResourceType, theTransactionDetails, theRequest, theResource);
|
||||
if (match.size() > 1) {
|
||||
|
|
|
@ -420,6 +420,9 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
@Transient
|
||||
private volatile String myCreatedByMatchUrl;
|
||||
|
||||
@Transient
|
||||
private volatile String myUpdatedByMatchUrl;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -1007,6 +1010,18 @@ public class ResourceTable extends BaseHasResource implements Serializable, IBas
|
|||
myCreatedByMatchUrl = theCreatedByMatchUrl;
|
||||
}
|
||||
|
||||
public String getUpdatedByMatchUrl() {
|
||||
return myUpdatedByMatchUrl;
|
||||
}
|
||||
|
||||
public void setUpdatedByMatchUrl(String theUpdatedByMatchUrl) {
|
||||
myUpdatedByMatchUrl = theUpdatedByMatchUrl;
|
||||
}
|
||||
|
||||
public boolean isVersionUpdatedInCurrentTransaction() {
|
||||
return myVersionUpdatedInCurrentTransaction;
|
||||
}
|
||||
|
||||
public void setLuceneIndexData(ExtendedHSearchIndexData theLuceneIndexData) {
|
||||
myLuceneIndexData = theLuceneIndexData;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,9 @@ import org.hl7.fhir.r4.model.Organization;
|
|||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Resource;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import javax.persistence.Id;
|
||||
|
@ -48,6 +50,7 @@ import java.util.ArrayList;
|
|||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
@ -63,6 +66,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
|
@ -120,15 +124,15 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p.getMeta().addTag("system", "coding", "display");
|
||||
|
||||
myMemoryCacheService.invalidateAllCaches();
|
||||
myPatientDao.create(p, new SystemRequestDetails());
|
||||
myPatientDao.create(p, mySrd);
|
||||
//inject conflicting.
|
||||
myTagDefinitionDao.saveAndFlush(def);
|
||||
myMemoryCacheService.invalidateAllCaches();
|
||||
|
||||
myPatientDao.create(p, new SystemRequestDetails());
|
||||
myPatientDao.create(p, mySrd);
|
||||
myMemoryCacheService.invalidateAllCaches();
|
||||
|
||||
myPatientDao.create(p, new SystemRequestDetails());
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
}
|
||||
|
||||
|
@ -139,35 +143,35 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
IIdType id = myPatientDao.create(p).getId().toUnqualified();
|
||||
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualified();
|
||||
|
||||
p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
p.setActive(true);
|
||||
IIdType id2 = myPatientDao.create(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified();
|
||||
IIdType id2 = myPatientDao.create(p, "Patient?identifier=urn:system|" + methodName + "2", mySrd).getId().toUnqualified();
|
||||
assertEquals(id.getValue(), id2.getValue());
|
||||
|
||||
p = new Patient();
|
||||
p.setId(id);
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
p.setActive(false);
|
||||
myPatientDao.update(p).getId();
|
||||
myPatientDao.update(p, mySrd);
|
||||
|
||||
p.setActive(true);
|
||||
id2 = myPatientDao.update(p, "Patient?identifier=urn:system|" + methodName + "2").getId().toUnqualified();
|
||||
id2 = myPatientDao.update(p, "Patient?identifier=urn:system|" + methodName + "2", mySrd).getId().toUnqualified();
|
||||
assertEquals(id.getIdPart(), id2.getIdPart());
|
||||
assertEquals("3", id2.getVersionIdPart());
|
||||
|
||||
Patient newPatient = myPatientDao.read(id);
|
||||
Patient newPatient = myPatientDao.read(id, mySrd);
|
||||
assertEquals("1", newPatient.getIdElement().getVersionIdPart());
|
||||
|
||||
newPatient = myPatientDao.read(id.toVersionless());
|
||||
newPatient = myPatientDao.read(id.toVersionless(), mySrd);
|
||||
assertEquals("3", newPatient.getIdElement().getVersionIdPart());
|
||||
|
||||
myPatientDao.delete(id.toVersionless());
|
||||
myPatientDao.delete(id.toVersionless(), mySrd);
|
||||
|
||||
try {
|
||||
myPatientDao.read(id.toVersionless());
|
||||
myPatientDao.read(id.toVersionless(), mySrd);
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// nothing
|
||||
|
@ -175,6 +179,92 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class TestConditionalResourceMustMatchConditionForUpdate {
|
||||
|
||||
private final Patient myPatient = new Patient();
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
myPatient.setId("existing-patient");
|
||||
myPatient.addIdentifier().setSystem("http://kookaburra.text/id").setValue("kookaburra1");
|
||||
myPatientDao.update(myPatient, mySrd);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
myStorageSettings.setPreventInvalidatingConditionalMatchCriteria(false);
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class ForFirstVersion {
|
||||
|
||||
// For first version must fail validation no matter the state of PreventInvalidatingConditionalMatchCriteria
|
||||
|
||||
@Test
|
||||
public void withPreventInvalidatingConditionalMatchCriteria_true_mustThrow() {
|
||||
//Note this should always default to false to preserve existing behaviour
|
||||
myStorageSettings.setPreventInvalidatingConditionalMatchCriteria(true);
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("http://kookaburra.text/id").setValue("kookaburra1");
|
||||
|
||||
InvalidRequestException thrown = assertThrows(InvalidRequestException.class,
|
||||
() -> myPatientDao.update(p2,
|
||||
"Patient?identifier=http://kookaburra.text/id|kookaburra2", mySrd));
|
||||
assertThat(thrown.getMessage(), endsWith(
|
||||
"Failed to process conditional create. The supplied resource did not satisfy the conditional URL."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPreventInvalidatingConditionalMatchCriteria_false_mustThrow() {
|
||||
//Note this should always default to false to preserve existing behaviour
|
||||
assertFalse(myStorageSettings.isPreventInvalidatingConditionalMatchCriteria());
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("http://kookaburra.text/id").setValue("kookaburra1");
|
||||
|
||||
InvalidRequestException thrown = assertThrows(InvalidRequestException.class,
|
||||
() -> myPatientDao.update(p2,
|
||||
"Patient?identifier=http://kookaburra.text/id|kookaburra2", mySrd));
|
||||
assertThat(thrown.getMessage(), endsWith(
|
||||
"Failed to process conditional create. The supplied resource did not satisfy the conditional URL."));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
public class ForOtherThanFirstVersion {
|
||||
|
||||
// For other than first version must fail validation only when PreventInvalidatingConditionalMatchCriteria is true
|
||||
|
||||
@Test
|
||||
public void withPreventInvalidatingConditionalMatchCriteria_false_mustWork() {
|
||||
//Note this should always default to false to preserve existing behaviour
|
||||
assertFalse(myStorageSettings.isPreventInvalidatingConditionalMatchCriteria());
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("http://kookaburra.text/id").setValue("kookaburra2");
|
||||
|
||||
myPatientDao.update(p2, "Patient?identifier=http://kookaburra.text/id|kookaburra1", mySrd);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPreventInvalidatingConditionalMatchCriteria_true_mustThrow() {
|
||||
myStorageSettings.setPreventInvalidatingConditionalMatchCriteria(true); //Note this should always default to false to preserve existing behaviour
|
||||
|
||||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("http://kookaburra.text/id").setValue("kookaburra2");
|
||||
|
||||
InvalidRequestException thrown = assertThrows(InvalidRequestException.class,
|
||||
() -> myPatientDao.update(p2,
|
||||
"Patient?identifier=http://kookaburra.text/id|kookaburra1", mySrd));
|
||||
assertThat(thrown.getMessage(), endsWith(
|
||||
"Failed to process conditional update. The supplied resource did not satisfy the conditional URL."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateConditionalOnEmailParameterWithPlusSymbol() {
|
||||
IBundleProvider outcome;
|
||||
|
@ -184,21 +274,20 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p.addTelecom()
|
||||
.setSystem(ContactPoint.ContactPointSystem.EMAIL)
|
||||
.setValue("help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im+a@bug.com", mySrd);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous());
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous(), mySrd);
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
|
||||
p = new Patient();
|
||||
p.addTelecom()
|
||||
.setSystem(ContactPoint.ContactPointSystem.EMAIL)
|
||||
.setValue("help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im+a@bug.com", mySrd);
|
||||
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous());
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous(), mySrd);
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -210,19 +299,19 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p.addTelecom()
|
||||
.setSystem(ContactPoint.ContactPointSystem.EMAIL)
|
||||
.setValue("help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im%2Ba@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im%2Ba@bug.com", mySrd);
|
||||
myCaptureQueriesListener.logSelectQueries();
|
||||
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous());
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous(), mySrd);
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
|
||||
p = new Patient();
|
||||
p.addTelecom()
|
||||
.setSystem(ContactPoint.ContactPointSystem.EMAIL)
|
||||
.setValue("help-im+a@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im%2Ba@bug.com");
|
||||
myPatientDao.update(p, "Patient?email=help-im%2Ba@bug.com", mySrd);
|
||||
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous());
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous(), mySrd);
|
||||
assertEquals(1, outcome.sizeOrThrowNpe());
|
||||
|
||||
}
|
||||
|
@ -236,7 +325,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("sys1").setValue("val1");
|
||||
p.addName().setFamily("FAMILY1");
|
||||
IIdType id = myPatientDao.create(p).getId().toUnqualifiedVersionless();
|
||||
IIdType id = myPatientDao.create(p, mySrd).getId().toUnqualifiedVersionless();
|
||||
|
||||
runInTransaction(() -> {
|
||||
myEntityManager.createQuery("UPDATE ResourceIndexedSearchParamString s SET s.myHashIdentity = null").executeUpdate();
|
||||
|
@ -252,12 +341,12 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p.setId(id);
|
||||
p.addIdentifier().setSystem("sys2").setValue("val2");
|
||||
p.addName().setFamily("FAMILY2");
|
||||
myPatientDao.update(p);
|
||||
myPatientDao.update(p, mySrd);
|
||||
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.setLoadSynchronous(true);
|
||||
map.add(Patient.SP_FAMILY, new StringParam("FAMILY2"));
|
||||
Patient newPatient = (Patient) myPatientDao.search(map).getResources(0, 1).get(0);
|
||||
Patient newPatient = (Patient) myPatientDao.search(map, mySrd).getResources(0, 1).get(0);
|
||||
assertEquals("FAMILY2", newPatient.getName().get(0).getFamily());
|
||||
}
|
||||
|
||||
|
@ -266,7 +355,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
IIdType id = runInTransaction(() -> {
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||
return myPatientDao.create(p).getId().toUnqualified();
|
||||
return myPatientDao.create(p, mySrd).getId().toUnqualified();
|
||||
});
|
||||
|
||||
String createTime = runInTransaction(() -> {
|
||||
|
@ -286,7 +375,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
Patient p = new Patient();
|
||||
p.setId(id.getIdPart());
|
||||
p.addIdentifier().setSystem("urn:system").setValue("2");
|
||||
myPatientDao.update(p).getResource();
|
||||
myPatientDao.update(p, mySrd);
|
||||
});
|
||||
|
||||
runInTransaction(() -> {
|
||||
|
@ -415,7 +504,6 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
public void testHardMetaCapIsEnforcedOnCreate() {
|
||||
myStorageSettings.setResourceMetaCountHardLimit(3);
|
||||
|
||||
IIdType id;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.getMeta().addTag().setSystem("http://foo").setCode("1");
|
||||
|
@ -424,7 +512,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
patient.getMeta().addTag().setSystem("http://foo").setCode("4");
|
||||
patient.setActive(true);
|
||||
try {
|
||||
id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
myPatientDao.create(patient, mySrd);
|
||||
fail();
|
||||
} catch (UnprocessableEntityException e) {
|
||||
assertEquals(Msg.code(932) + "Resource contains 4 meta entries (tag/profile/security label), maximum is 3", e.getMessage());
|
||||
|
@ -466,33 +554,33 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
Patient p = new Patient();
|
||||
p.setActive(true);
|
||||
p.setId("Patient/A");
|
||||
String id = myPatientDao.update(p).getId().getValue();
|
||||
String id = myPatientDao.update(p, mySrd).getId().getValue();
|
||||
assertThat(id, endsWith("Patient/A/_history/1"));
|
||||
|
||||
// Second time should not result in an update
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
p.setId("Patient/A");
|
||||
id = myPatientDao.update(p).getId().getValue();
|
||||
id = myPatientDao.update(p, mySrd).getId().getValue();
|
||||
assertThat(id, endsWith("Patient/A/_history/1"));
|
||||
|
||||
// And third time should not result in an update
|
||||
p = new Patient();
|
||||
p.setActive(true);
|
||||
p.setId("Patient/A");
|
||||
id = myPatientDao.update(p).getId().getValue();
|
||||
id = myPatientDao.update(p, mySrd).getId().getValue();
|
||||
assertThat(id, endsWith("Patient/A/_history/1"));
|
||||
|
||||
myPatientDao.read(new IdType("Patient/A"));
|
||||
myPatientDao.read(new IdType("Patient/A/_history/1"));
|
||||
myPatientDao.read(new IdType("Patient/A"), mySrd);
|
||||
myPatientDao.read(new IdType("Patient/A/_history/1"), mySrd);
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/A/_history/2"));
|
||||
myPatientDao.read(new IdType("Patient/A/_history/2"), mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
}
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/A/_history/3"));
|
||||
myPatientDao.read(new IdType("Patient/A/_history/3"), mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
// good
|
||||
|
@ -502,7 +590,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p = new Patient();
|
||||
p.setActive(false);
|
||||
p.setId("Patient/A");
|
||||
id = myPatientDao.update(p).getId().getValue();
|
||||
id = myPatientDao.update(p, mySrd).getId().getValue();
|
||||
assertThat(id, endsWith("Patient/A/_history/2"));
|
||||
|
||||
}
|
||||
|
@ -554,7 +642,8 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
IBundleProvider historyBundle = myPatientDao.history(outcome.getId(), null, null, null, mySrd);
|
||||
|
||||
assertEquals(2, historyBundle.size().intValue());
|
||||
assertNotNull(historyBundle);
|
||||
assertEquals(2, Objects.requireNonNull(historyBundle.size()).intValue());
|
||||
|
||||
List<IBaseResource> history = historyBundle.getResources(0, 2);
|
||||
|
||||
|
@ -600,7 +689,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
myPatientDao.create(p, mySrd).getId();
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
InstantDt start = InstantDt.withCurrentTime();
|
||||
ourLog.info("First time: {}", start.getValueAsString());
|
||||
|
@ -634,7 +723,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
myPatientDao.create(p, mySrd).getId();
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
InstantDt start = InstantDt.withCurrentTime();
|
||||
ourLog.info("First time: {}", start.getValueAsString());
|
||||
|
@ -685,7 +774,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
Patient p = new Patient();
|
||||
try {
|
||||
myPatientDao.update(p);
|
||||
myPatientDao.update(p, mySrd);
|
||||
} catch (InvalidRequestException e) {
|
||||
assertEquals(Msg.code(987) + "Can not update resource of type Patient as it has no ID", e.getMessage());
|
||||
}
|
||||
|
@ -700,7 +789,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
|
||||
Patient p = new Patient();
|
||||
p.addIdentifier().setSystem("urn:system").setValue(methodName + "2");
|
||||
myPatientDao.create(p, mySrd).getId();
|
||||
myPatientDao.create(p, mySrd);
|
||||
|
||||
InstantDt start = InstantDt.withCurrentTime();
|
||||
Thread.sleep(100);
|
||||
|
@ -781,7 +870,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
Patient p2 = new Patient();
|
||||
p2.addIdentifier().setSystem("urn:system").setValue("testUpdateMaintainsSearchParamsDstu2BBB");
|
||||
p2.addName().setFamily("Tester").addGiven("testUpdateMaintainsSearchParamsDstu2BBB");
|
||||
myPatientDao.create(p2, mySrd).getId();
|
||||
myPatientDao.create(p2, mySrd);
|
||||
|
||||
List<JpaPid> ids = myPatientDao.searchForIds(new SearchParameterMap(Patient.SP_GIVEN, new StringParam("testUpdateMaintainsSearchParamsDstu2AAA")), null);
|
||||
assertEquals(1, ids.size());
|
||||
|
@ -1152,7 +1241,7 @@ public class FhirResourceDaoR4UpdateTest extends BaseJpaR4Test {
|
|||
p.setId(UUID.randomUUID().toString());
|
||||
p.addName().setFamily("FAM");
|
||||
try {
|
||||
myPatientDao.update(p);
|
||||
myPatientDao.update(p, mySrd);
|
||||
fail();
|
||||
} catch (ResourceNotFoundException e) {
|
||||
assertThat(e.getMessage(), matchesPattern(Msg.code(959) + "No resource exists on this server resource with ID.*, and client-assigned IDs are not enabled."));
|
||||
|
|
|
@ -107,6 +107,9 @@ public class JpaStorageSettings extends StorageSettings {
|
|||
* Child Configurations
|
||||
*/
|
||||
private static final Integer DEFAULT_INTERNAL_SYNCHRONOUS_SEARCH_SIZE = 10000;
|
||||
|
||||
private static final boolean DEFAULT_PREVENT_INVALIDATING_CONDITIONAL_MATCH_CRITERIA = false;
|
||||
|
||||
/**
|
||||
* Do not change default of {@code 0}!
|
||||
*
|
||||
|
@ -331,6 +334,16 @@ public class JpaStorageSettings extends StorageSettings {
|
|||
*/
|
||||
private boolean myResourceHistoryDbEnabled = true;
|
||||
|
||||
/**
|
||||
* This setting allows preventing a conditional update to invalidate the match criteria.
|
||||
* <p/>
|
||||
* By default, this is disabled unless explicitly enabled.
|
||||
*
|
||||
* @since 6.8.2
|
||||
*/
|
||||
private boolean myPreventInvalidatingConditionalMatchCriteria =
|
||||
DEFAULT_PREVENT_INVALIDATING_CONDITIONAL_MATCH_CRITERIA;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -354,6 +367,9 @@ public class JpaStorageSettings extends StorageSettings {
|
|||
if (HapiSystemProperties.isUnitTestModeEnabled()) {
|
||||
setJobFastTrackingEnabled(true);
|
||||
}
|
||||
if (HapiSystemProperties.isPreventInvalidatingConditionalMatchCriteria()) {
|
||||
setPreventInvalidatingConditionalMatchCriteria(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2372,6 +2388,14 @@ public class JpaStorageSettings extends StorageSettings {
|
|||
myNonResourceDbHistoryEnabled = theNonResourceDbHistoryEnabled;
|
||||
}
|
||||
|
||||
public void setPreventInvalidatingConditionalMatchCriteria(boolean theCriteria) {
|
||||
myPreventInvalidatingConditionalMatchCriteria = theCriteria;
|
||||
}
|
||||
|
||||
public boolean isPreventInvalidatingConditionalMatchCriteria() {
|
||||
return myPreventInvalidatingConditionalMatchCriteria;
|
||||
}
|
||||
|
||||
public enum StoreMetaSourceInformationEnum {
|
||||
NONE(false, false),
|
||||
SOURCE_URI(true, false),
|
||||
|
|
Loading…
Reference in New Issue