New extension for auto versioning references of the resource (#5591)

New extension for auto-versioning references of the resource
This commit is contained in:
volodymyr-korzh 2024-01-12 17:00:58 -07:00 committed by GitHub
parent 763894c28f
commit 5286829585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 665 additions and 481 deletions

View File

@ -43,6 +43,7 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.MetaUtil;
import ca.uhn.fhir.util.UrlUtil;
import com.google.common.base.Charsets;
import jakarta.annotation.Nullable;
@ -217,7 +218,8 @@ public abstract class BaseParser implements IParser {
});
}
private String determineReferenceText(IBaseReference theRef, CompositeChildElement theCompositeChildElement) {
private String determineReferenceText(
IBaseReference theRef, CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
IIdType ref = theRef.getReferenceElement();
if (isBlank(ref.getIdPart())) {
String reference = ref.getValue();
@ -241,7 +243,7 @@ public abstract class BaseParser implements IParser {
.getResourceDefinition(theRef.getResource())
.getName());
}
if (isStripVersionsFromReferences(theCompositeChildElement)) {
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
reference = refId.toVersionless().getValue();
} else {
reference = refId.getValue();
@ -258,12 +260,12 @@ public abstract class BaseParser implements IParser {
myContext.getResourceDefinition(theRef.getResource()).getName());
}
if (isNotBlank(myServerBaseUrl) && StringUtils.equals(myServerBaseUrl, ref.getBaseUrl())) {
if (isStripVersionsFromReferences(theCompositeChildElement)) {
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
return ref.toUnqualifiedVersionless().getValue();
}
return ref.toUnqualified().getValue();
}
if (isStripVersionsFromReferences(theCompositeChildElement)) {
if (isStripVersionsFromReferences(theCompositeChildElement, theResource)) {
return ref.toVersionless().getValue();
}
return ref.getValue();
@ -604,7 +606,17 @@ public abstract class BaseParser implements IParser {
return myContext.getParserOptions().isOverrideResourceIdWithBundleEntryFullUrl();
}
private boolean isStripVersionsFromReferences(CompositeChildElement theCompositeChildElement) {
private boolean isStripVersionsFromReferences(
CompositeChildElement theCompositeChildElement, IBaseResource theResource) {
Set<String> autoVersionReferencesAtPathExtensions =
MetaUtil.getAutoVersionReferencesAtPath(theResource.getMeta(), myContext.getResourceType(theResource));
if (!autoVersionReferencesAtPathExtensions.isEmpty()
&& theCompositeChildElement.anyPathMatches(autoVersionReferencesAtPathExtensions)) {
return false;
}
Boolean stripVersionsFromReferences = myStripVersionsFromReferences;
if (stripVersionsFromReferences != null) {
return stripVersionsFromReferences;
@ -811,7 +823,7 @@ public abstract class BaseParser implements IParser {
*/
if (next instanceof IBaseReference) {
IBaseReference nextRef = (IBaseReference) next;
String refText = determineReferenceText(nextRef, theCompositeChildElement);
String refText = determineReferenceText(nextRef, theCompositeChildElement, theResource);
if (!StringUtils.equals(refText, nextRef.getReferenceElement().getValue())) {
if (retVal == theValues) {

View File

@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
@ -177,15 +178,18 @@ public class ExtensionUtil {
* pulls out any extensions that have the given theExtensionUrl and a primitive value type,
* and returns a list of the string version of the extension values.
*/
public static List<String> getExtensionPrimitiveValues(IBaseHasExtensions theBase, String theExtensionUrl) {
List<String> values = theBase.getExtension().stream()
public static List<String> getExtensionPrimitiveValues(IBase theBase, String theExtensionUrl) {
if (theBase instanceof IBaseHasExtensions) {
return ((IBaseHasExtensions) theBase)
.getExtension().stream()
.filter(t -> theExtensionUrl.equals(t.getUrl()))
.filter(t -> t.getValue() instanceof IPrimitiveType<?>)
.map(t -> (IPrimitiveType<?>) t.getValue())
.map(IPrimitiveType::getValueAsString)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
return values;
}
return Collections.emptyList();
}
/**

View File

@ -162,6 +162,16 @@ public class HapiExtensions {
*/
public static final String EXTENSION_SEARCHPARAM_UPLIFT_REFCHAIN =
"https://smilecdr.com/fhir/ns/StructureDefinition/searchparameter-uplift-refchain";
/**
* This extension is used to enable auto version references at path for resource instances.
* This extension should be of type <code>string</code> and should be
* placed on the <code>Resource.meta</code> element.
* It is allowed to add multiple extensions with different paths.
*/
public static final String EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH =
"http://hapifhir.io/fhir/StructureDefinition/auto-version-references-at-path";
/**
* This extension is used for "uplifted refchains" on search parameters. See the
* HAPI FHIR documentation for an explanation of how these work.

View File

@ -35,6 +35,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -144,4 +146,12 @@ public class MetaUtil {
}
sourceElement.setValueAsString(theValue);
}
public static Set<String> getAutoVersionReferencesAtPath(IBaseMetaType theMeta, String theResourceType) {
return ExtensionUtil.getExtensionPrimitiveValues(
theMeta, HapiExtensions.EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH)
.stream()
.map(path -> String.format("%s.%s", theResourceType, path))
.collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,5 @@
---
type: add
issue: 5588
title: "Added `auto-version-references-at-path` extension that allows to
enable auto versioning references at specified paths of resource instances."

View File

@ -166,3 +166,22 @@ You can also configure HAPI to not strip versions only on certain fields. This i
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|disableStripVersionsField}}
```
# Automatically Versioned References
It is possible to configure HAPI to automatically version references for desired resource instances by providing the `auto-version-references-at-path` extension in the `Resource.meta` element:
```json
"meta": {
"extension":[
{
"url":"http://hapifhir.io/fhir/StructureDefinition/auto-version-references-at-path",
"valueString":"focus"
}
]
}
```
It is allowed to add multiple extensions with different paths. When a resource is stored, any references found at the specified paths will have the current version of the target appended, if a version is not already present.
Parser will not strip versions from references at paths provided by the `auto-version-references-at-path` extension.

View File

@ -2,11 +2,11 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.model.DaoMethodOutcome;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.test.BaseJpaR4Test;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.SystemRequestDetails;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
@ -18,20 +18,28 @@ import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Condition;
import org.hl7.fhir.r4.model.Encounter;
import org.hl7.fhir.r4.model.ExplanationOfBenefit;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.Organization;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.StringType;
import org.hl7.fhir.r4.model.Task;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.platform.commons.annotation.Testable;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
@ -39,6 +47,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static ca.uhn.fhir.util.HapiExtensions.EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
@ -60,11 +69,60 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
myStorageSettings.setAutoVersionReferenceAtPaths(new JpaStorageSettings().getAutoVersionReferenceAtPaths());
}
@Test
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToUpsertWithNop() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths("ExplanationOfBenefit.patient");
@Nested
public class AutoVersionReferencesWithSettingAndExtension extends AutoVersionReferencesWithExtension {
@BeforeEach
public void before() {
beforeAutoVersionReferencesWithSetting();
}
}
@Nested
public class AutoVersionReferencesWithSetting extends AutoVersionReferencesTestCases {
@BeforeEach
public void before() {
beforeAutoVersionReferencesWithSetting();
}
}
private void beforeAutoVersionReferencesWithSetting() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths(
"Patient.managingOrganization",
"ExplanationOfBenefit.patient",
"Observation.subject",
"MessageHeader.focus"
);
}
@Nested
public class AutoVersionReferencesWithExtension extends AutoVersionReferencesTestCases {
@BeforeEach
public void before() {
patientAutoVersionExtension = createAutoVersionReferencesExtensions("managingOrganization");
observationAutoVersionExtension = createAutoVersionReferencesExtensions("subject");
explanationOfBenefitAutoVersionExtension = createAutoVersionReferencesExtensions("patient");
messageHeaderAutoVersionExtension = createAutoVersionReferencesExtensions("focus");
}
@NotNull
private List<Extension> createAutoVersionReferencesExtensions(String... thePaths) {
return Arrays.stream(thePaths)
.map(path -> new Extension(EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH, new StringType(path)))
.collect(Collectors.toList());
}
}
@Testable
public abstract class AutoVersionReferencesTestCases {
protected List<Extension> patientAutoVersionExtension = Collections.emptyList();
protected List<Extension> observationAutoVersionExtension = Collections.emptyList();
protected List<Extension> explanationOfBenefitAutoVersionExtension = Collections.emptyList();
protected List<Extension> messageHeaderAutoVersionExtension = Collections.emptyList();
@Test
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToUpsertWithNoOp() {
// We'll submit the same bundle twice. It has an UPSERT (with no changes
// the second time) on a Patient, and a CREATE on an ExplanationOfBenefit
// referencing that Patient.
@ -77,6 +135,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
bb.addTransactionUpdateEntry(patient);
ExplanationOfBenefit eob = new ExplanationOfBenefit();
eob.getMeta().setExtension(explanationOfBenefitAutoVersionExtension);
eob.setId(IdType.newRandomUuid());
eob.setPatient(new Reference("Patient/A"));
bb.addTransactionCreateEntry(eob);
@ -104,13 +163,7 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
}
@Test
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToVersionedReferenceToUpsertWithNop() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths(
"Patient.managingOrganization",
"ExplanationOfBenefit.patient"
);
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToVersionedReferenceToUpsertWithNoOp() {
// We'll submit the same bundle twice. It has an UPSERT (with no changes
// the second time) on a Patient, and a CREATE on an ExplanationOfBenefit
// referencing that Patient.
@ -123,12 +176,14 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
bb.addTransactionUpdateEntry(organization);
Patient patient = new Patient();
patient.getMeta().setExtension(patientAutoVersionExtension);
patient.setId("Patient/A");
patient.setManagingOrganization(new Reference("Organization/O"));
patient.setActive(true);
bb.addTransactionUpdateEntry(patient);
ExplanationOfBenefit eob = new ExplanationOfBenefit();
eob.getMeta().setExtension(explanationOfBenefitAutoVersionExtension);
eob.setId(IdType.newRandomUuid());
eob.setPatient(new Reference("Patient/A"));
bb.addTransactionCreateEntry(eob);
@ -165,12 +220,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
@Test
public void testCreateAndUpdateVersionedReferencesInTransaction_VersionedReferenceToVersionedReferenceToUpsertWithChange() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths(
"Patient.managingOrganization",
"ExplanationOfBenefit.patient"
);
AtomicInteger counter = new AtomicInteger();
Supplier<Bundle> supplier = () -> {
BundleBuilder bb = new BundleBuilder(myFhirContext);
@ -182,12 +231,14 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
bb.addTransactionUpdateEntry(organization);
Patient patient = new Patient();
patient.getMeta().setExtension(patientAutoVersionExtension);
patient.setId("Patient/A");
patient.setManagingOrganization(new Reference("Organization/O"));
patient.setActive(true);
bb.addTransactionUpdateEntry(patient);
ExplanationOfBenefit eob = new ExplanationOfBenefit();
eob.getMeta().setExtension(explanationOfBenefitAutoVersionExtension);
eob.setId(IdType.newRandomUuid());
eob.setPatient(new Reference("Patient/A"));
bb.addTransactionCreateEntry(eob);
@ -220,6 +271,325 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
assertEquals(patientId, eob2.getPatient().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath() {
Patient p = new Patient();
p.setActive(true);
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
assertEquals("1", patientId.getVersionIdPart());
assertEquals(null, patientId.getBaseUrl());
String patientIdString = patientId.getValue();
// Create - put an unversioned reference in the subject
Observation observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.getSubject().setReference(patientId.toVersionless().getValue());
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientIdString, observation.getSubject().getReference());
myCaptureQueriesListener.clear();
// Update - put an unversioned reference in the subject
observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.setId(observationId);
observation.addIdentifier().setSystem("http://foo").setValue("bar");
observation.getSubject().setReference(patientId.toVersionless().getValue());
myObservationDao.update(observation);
// Make sure we're not introducing any extra DB operations
assertEquals(5, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientIdString, observation.getSubject().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_SourceAndTargetBothCreated() {
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
builder.addTransactionCreateEntry(patient);
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
builder.addTransactionCreateEntry(encounter);
Observation observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.getSubject().setReference(patient.getId()); // versioned
observation.getEncounter().setReference(encounter.getId()); // not versioned
builder.addTransactionCreateEntry(observation);
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
assertTrue(patientId.hasVersionIdPart());
assertTrue(encounterId.hasVersionIdPart());
assertTrue(observationId.hasVersionIdPart());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetConditionalCreatedNoOp() {
{
// Create patient
createAndUpdatePatient(IdType.newRandomUuid().getId());
// Create encounter
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
myEncounterDao.create(encounter);
}
// Verify Patient Version
assertEquals("2", myPatientDao.search(SearchParameterMap.newSynchronous("active", new TokenParam("false")))
.getResources(0, 1).get(0).getIdElement().getVersionIdPart());
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
builder.addTransactionCreateEntry(patient).conditional("Patient?active=false");
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
builder.addTransactionCreateEntry(encounter).conditional("Encounter?identifier=http://baz|baz");
Observation observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.getSubject().setReference(patient.getId()); // versioned
observation.getEncounter().setReference(encounter.getId()); // not versioned
builder.addTransactionCreateEntry(observation);
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("200 OK", outcome.getEntry().get(1).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(2).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
assertEquals("2", patientId.getVersionIdPart());
assertEquals("1", encounterId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals("2", observation.getSubject().getReferenceElement().getVersionIdPart());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() {
myStorageSettings.setDeleteEnabled(false);
{
// Create patient
Patient patient = new Patient();
patient.setId("PATIENT");
patient.setActive(true);
myPatientDao.update(patient).getId();
// Update patient to make a second version
patient.setActive(false);
myPatientDao.update(patient);
}
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId("Patient/PATIENT");
patient.setActive(true);
builder.addTransactionUpdateEntry(patient);
Observation observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.getSubject().setReference(patient.getId()); // versioned
builder.addTransactionCreateEntry(observation);
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertEquals("3", patientId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Make sure we're not introducing any extra DB operations
assertEquals(3, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditional() {
createAndUpdatePatient(IdType.newRandomUuid().getId());
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setDeceased(new BooleanType(true));
patient.setActive(false);
builder
.addTransactionUpdateEntry(patient)
.conditional("Patient?active=false");
Observation observation = new Observation();
observation.getMeta().setExtension(observationAutoVersionExtension);
observation.getSubject().setReference(patient.getId()); // versioned
builder.addTransactionCreateEntry(observation);
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertEquals("3", patientId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Make sure we're not introducing any extra DB operations
assertEquals(4, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
}
@Test
@DisplayName("Bundle transaction with AutoVersionReferenceAtPath on and with existing Patient resource should create")
public void bundleTransaction_autoVersionReferenceAtPathWithPreexistingPatientReference_shouldCreate() {
String patientId = "Patient/RED";
IIdType idType = new IdDt(patientId);
// create patient ahead of time
Patient patient = new Patient();
patient.setId(patientId);
DaoMethodOutcome outcome = myPatientDao.update(patient);
assertThat(outcome.getResource().getIdElement().getValue(), is(equalTo(patientId + "/_history/1")));
Patient returned = myPatientDao.read(idType);
Assertions.assertNotNull(returned);
assertThat(returned.getId(), is(equalTo(patientId + "/_history/1")));
// update to change version
patient.setActive(true);
myPatientDao.update(patient);
Observation obs = new Observation();
obs.getMeta().setExtension(observationAutoVersionExtension);
obs.setId("Observation/DEF");
Reference patientRef = new Reference(patientId);
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirContext);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle) builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
}
@Test
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
public void testNoNpeMinimal() {
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
Observation obs = new Observation();
obs.getMeta().setExtension(observationAutoVersionExtension);
obs.setId("Observation/DEF");
Reference patientRef = new Reference("Patient/RED");
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirContext);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle) builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
Assertions.assertNotNull(returned);
}
@Test
public void testInsertVersionedReferencesByPath_resourceReferenceNotInTransaction_addsVersionToTheReferences() {
Patient patient = createAndUpdatePatient(IdType.newRandomUuid().getId());
// create MessageHeader
MessageHeader messageHeader = new MessageHeader();
messageHeader.getMeta().setExtension(messageHeaderAutoVersionExtension);
// add reference
messageHeader.addFocus().setReference(patient.getIdElement().toVersionless().getValue());
BundleBuilder builder = new BundleBuilder(myFhirContext);
builder.addTransactionCreateEntry(messageHeader);
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(builder.getBundle()));
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.info(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("201 Created", outcome.getEntry().get(0).getResponse().getStatus());
IdType messageHeaderId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
assertEquals("2", patient.getIdElement().getVersionIdPart());
assertEquals("1", messageHeaderId.getVersionIdPart());
// read back and verify that reference is versioned
messageHeader = myMessageHeaderDao.read(messageHeaderId);
assertEquals(patient.getIdElement().getValue(), messageHeader.getFocus().get(0).getReference());
}
private Patient createAndUpdatePatient(String thePatientId) {
Patient patient = new Patient();
patient.setId(thePatientId);
patient.setActive(true);
myPatientDao.create(patient).getId();
// update patient to make a second version
patient.setActive(false);
myPatientDao.update(patient);
return patient;
}
}
@Test
public void testStoreAndRetrieveVersionedReference() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
@ -264,243 +634,6 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
assertEquals(patientId.withVersion("1").getValue(), observation.getSubject().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
Patient p = new Patient();
p.setActive(true);
IIdType patientId = myPatientDao.create(p).getId().toUnqualified();
assertEquals("1", patientId.getVersionIdPart());
assertEquals(null, patientId.getBaseUrl());
String patientIdString = patientId.getValue();
// Create - put an unversioned reference in the subject
Observation observation = new Observation();
observation.getSubject().setReference(patientId.toVersionless().getValue());
IIdType observationId = myObservationDao.create(observation).getId().toUnqualified();
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientIdString, observation.getSubject().getReference());
myCaptureQueriesListener.clear();
// Update - put an unversioned reference in the subject
observation = new Observation();
observation.setId(observationId);
observation.addIdentifier().setSystem("http://foo").setValue("bar");
observation.getSubject().setReference(patientId.toVersionless().getValue());
myObservationDao.update(observation);
// Make sure we're not introducing any extra DB operations
assertEquals(5, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientIdString, observation.getSubject().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_SourceAndTargetBothCreated() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
builder.addTransactionCreateEntry(patient);
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
builder.addTransactionCreateEntry(encounter);
Observation observation = new Observation();
observation.getSubject().setReference(patient.getId()); // versioned
observation.getEncounter().setReference(encounter.getId()); // not versioned
builder.addTransactionCreateEntry(observation);
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
assertTrue(patientId.hasVersionIdPart());
assertTrue(encounterId.hasVersionIdPart());
assertTrue(observationId.hasVersionIdPart());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetConditionalCreatedNop() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
{
// Create patient
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
myPatientDao.create(patient).getId();
// Update patient to make a second version
patient.setActive(false);
myPatientDao.update(patient);
// Create encounter
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
myEncounterDao.create(encounter);
}
// Verify Patient Version
assertEquals("2", myPatientDao.search(SearchParameterMap.newSynchronous("active", new TokenParam("false"))).getResources(0, 1).get(0).getIdElement().getVersionIdPart());
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
builder.addTransactionCreateEntry(patient).conditional("Patient?active=false");
Encounter encounter = new Encounter();
encounter.setId(IdType.newRandomUuid());
encounter.addIdentifier().setSystem("http://baz").setValue("baz");
builder.addTransactionCreateEntry(encounter).conditional("Encounter?identifier=http://baz|baz");
Observation observation = new Observation();
observation.getSubject().setReference(patient.getId()); // versioned
observation.getEncounter().setReference(encounter.getId()); // not versioned
builder.addTransactionCreateEntry(observation);
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("200 OK", outcome.getEntry().get(1).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(2).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType encounterId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(2).getResponse().getLocation());
assertEquals("2", patientId.getVersionIdPart());
assertEquals("1", encounterId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
assertEquals("2", observation.getSubject().getReferenceElement().getVersionIdPart());
assertEquals(encounterId.toVersionless().getValue(), observation.getEncounter().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdate() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setDeleteEnabled(false);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
{
// Create patient
Patient patient = new Patient();
patient.setId("PATIENT");
patient.setActive(true);
myPatientDao.update(patient).getId();
// Update patient to make a second version
patient.setActive(false);
myPatientDao.update(patient);
}
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId("Patient/PATIENT");
patient.setActive(true);
builder.addTransactionUpdateEntry(patient);
Observation observation = new Observation();
observation.getSubject().setReference(patient.getId()); // versioned
builder.addTransactionCreateEntry(observation);
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertEquals("3", patientId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Make sure we're not introducing any extra DB operations
assertEquals(3, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
}
@Test
public void testInsertVersionedReferenceAtPath_InTransaction_TargetUpdateConditional() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
{
// Create patient
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setActive(true);
myPatientDao.create(patient).getId();
// Update patient to make a second version
patient.setActive(false);
myPatientDao.update(patient);
}
BundleBuilder builder = new BundleBuilder(myFhirContext);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.setDeceased(new BooleanType(true));
patient.setActive(false);
builder
.addTransactionUpdateEntry(patient)
.conditional("Patient?active=false");
Observation observation = new Observation();
observation.getSubject().setReference(patient.getId()); // versioned
builder.addTransactionCreateEntry(observation);
myCaptureQueriesListener.clear();
Bundle outcome = mySystemDao.transaction(mySrd, (Bundle) builder.getBundle());
ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));
assertEquals("200 OK", outcome.getEntry().get(0).getResponse().getStatus());
assertEquals("201 Created", outcome.getEntry().get(1).getResponse().getStatus());
IdType patientId = new IdType(outcome.getEntry().get(0).getResponse().getLocation());
IdType observationId = new IdType(outcome.getEntry().get(1).getResponse().getLocation());
assertEquals("3", patientId.getVersionIdPart());
assertEquals("1", observationId.getVersionIdPart());
// Make sure we're not introducing any extra DB operations
assertEquals(4, myCaptureQueriesListener.logSelectQueries().size());
// Read back and verify that reference is now versioned
observation = myObservationDao.read(observationId);
assertEquals(patientId.getValue(), observation.getSubject().getReference());
}
@Test
public void testSearchAndIncludeVersionedReference_Asynchronous() {
myFhirContext.getParserOptions().setStripVersionsFromReferences(false);
@ -866,70 +999,4 @@ public class FhirResourceDaoR4VersionedReferenceTest extends BaseJpaR4Test {
Observation obs = myObservationDao.read(idType);
Assertions.assertNotNull(obs);
}
@Test
@DisplayName("Bundle transaction with AutoVersionReferenceAtPath on and with existing Patient resource should create")
public void bundleTransaction_autoreferenceAtPathWithPreexistingPatientReference_shouldCreate() {
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
String patientId = "Patient/RED";
IIdType idType = new IdDt(patientId);
// create patient ahead of time
Patient patient = new Patient();
patient.setId(patientId);
DaoMethodOutcome outcome = myPatientDao.update(patient);
assertThat(outcome.getResource().getIdElement().getValue(), is(equalTo(patientId + "/_history/1")));
Patient returned = myPatientDao.read(idType);
Assertions.assertNotNull(returned);
assertThat(returned.getId(), is(equalTo(patientId + "/_history/1")));
// update to change version
patient.setActive(true);
myPatientDao.update(patient);
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference(patientId);
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirContext);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle)builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
}
@Test
@DisplayName("GH-2901 Test no NPE is thrown on autoversioned references")
public void testNoNpeMinimal() {
myStorageSettings.setAutoCreatePlaceholderReferenceTargets(true);
myStorageSettings.setAutoVersionReferenceAtPaths("Observation.subject");
Observation obs = new Observation();
obs.setId("Observation/DEF");
Reference patientRef = new Reference("Patient/RED");
obs.setSubject(patientRef);
BundleBuilder builder = new BundleBuilder(myFhirContext);
builder.addTransactionUpdateEntry(obs);
Bundle submitted = (Bundle)builder.getBundle();
Bundle returnedTr = mySystemDao.transaction(new SystemRequestDetails(), submitted);
Assertions.assertNotNull(returnedTr);
// some verification
Observation obRet = myObservationDao.read(obs.getIdElement());
Assertions.assertNotNull(obRet);
Patient returned = myPatientDao.read(patientRef.getReferenceElement());
Assertions.assertNotNull(returned);
}
}

View File

@ -157,6 +157,7 @@ import org.hl7.fhir.r4.model.Media;
import org.hl7.fhir.r4.model.Medication;
import org.hl7.fhir.r4.model.MedicationAdministration;
import org.hl7.fhir.r4.model.MedicationRequest;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.r4.model.Meta;
import org.hl7.fhir.r4.model.MolecularSequence;
import org.hl7.fhir.r4.model.NamingSystem;
@ -433,6 +434,9 @@ public abstract class BaseJpaR4Test extends BaseJpaTest implements ITestDataBuil
@Qualifier("myExplanationOfBenefitDaoR4")
protected IFhirResourceDao<ExplanationOfBenefit> myExplanationOfBenefitDao;
@Autowired
@Qualifier("myMessageHeaderDaoR4")
protected IFhirResourceDao<MessageHeader> myMessageHeaderDao;
@Autowired
protected IResourceTableDao myResourceTableDao;
@Autowired
protected IResourceHistoryTableDao myResourceHistoryTableDao;

View File

@ -59,7 +59,9 @@ import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.rest.server.util.ResourceSearchParams;
import ca.uhn.fhir.util.BundleUtil;
import ca.uhn.fhir.util.FhirTerser;
import ca.uhn.fhir.util.HapiExtensions;
import ca.uhn.fhir.util.IMetaTagSorter;
import ca.uhn.fhir.util.MetaUtil;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.util.ResourceReferenceInfo;
import ca.uhn.fhir.util.StopWatch;
@ -86,6 +88,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -690,29 +694,67 @@ public abstract class BaseStorageDao {
}
/**
* @see StorageSettings#getAutoVersionReferenceAtPaths()
* Extracts a list of references that should be auto-versioned.
*
* @return A set of references that should be versioned according to both storage settings
* and auto-version reference extensions, or it may also be empty.
*/
@Nonnull
public static Set<IBaseReference> extractReferencesToAutoVersion(
FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) {
Map<IBaseReference, Object> references = Collections.emptyMap();
Set<IBaseReference> referencesToAutoVersionFromConfig =
getReferencesToAutoVersionFromConfig(theFhirContext, theStorageSettings, theResource);
Set<IBaseReference> referencesToAutoVersionFromExtensions =
getReferencesToAutoVersionFromExtension(theFhirContext, theResource);
return Stream.concat(referencesToAutoVersionFromConfig.stream(), referencesToAutoVersionFromExtensions.stream())
.collect(Collectors.toMap(ref -> ref, ref -> ref, (oldRef, newRef) -> oldRef, IdentityHashMap::new))
.keySet();
}
/**
* Extracts a list of references that should be auto-versioned according to
* <code>auto-version-references-at-path</code> extensions.
* @see HapiExtensions#EXTENSION_AUTO_VERSION_REFERENCES_AT_PATH
*/
@Nonnull
private static Set<IBaseReference> getReferencesToAutoVersionFromExtension(
FhirContext theFhirContext, IBaseResource theResource) {
String resourceType = theFhirContext.getResourceType(theResource);
Set<String> autoVersionReferencesAtPaths =
MetaUtil.getAutoVersionReferencesAtPath(theResource.getMeta(), resourceType);
if (!autoVersionReferencesAtPaths.isEmpty()) {
return getReferencesWithoutVersionId(autoVersionReferencesAtPaths, theFhirContext, theResource);
}
return Collections.emptySet();
}
/**
* Extracts a list of references that should be auto-versioned according to storage configuration.
* @see StorageSettings#getAutoVersionReferenceAtPaths()
*/
@Nonnull
private static Set<IBaseReference> getReferencesToAutoVersionFromConfig(
FhirContext theFhirContext, StorageSettings theStorageSettings, IBaseResource theResource) {
if (!theStorageSettings.getAutoVersionReferenceAtPaths().isEmpty()) {
String resourceName = theFhirContext.getResourceType(theResource);
for (String nextPath : theStorageSettings.getAutoVersionReferenceAtPathsByResourceType(resourceName)) {
List<IBaseReference> nextReferences =
theFhirContext.newTerser().getValues(theResource, nextPath, IBaseReference.class);
for (IBaseReference next : nextReferences) {
if (next.getReferenceElement().hasVersionIdPart()) {
continue;
Set<String> autoVersionReferencesPaths =
theStorageSettings.getAutoVersionReferenceAtPathsByResourceType(resourceName);
return getReferencesWithoutVersionId(autoVersionReferencesPaths, theFhirContext, theResource);
}
if (references.isEmpty()) {
references = new IdentityHashMap<>();
return Collections.emptySet();
}
references.put(next, null);
}
}
}
return references.keySet();
private static Set<IBaseReference> getReferencesWithoutVersionId(
Set<String> autoVersionReferencesPaths, FhirContext theFhirContext, IBaseResource theResource) {
return autoVersionReferencesPaths.stream()
.map(fullPath -> theFhirContext.newTerser().getValues(theResource, fullPath, IBaseReference.class))
.flatMap(Collection::stream)
.filter(reference -> !reference.getReferenceElement().hasVersionIdPart())
.collect(Collectors.toMap(ref -> ref, ref -> ref, (oldRef, newRef) -> oldRef, IdentityHashMap::new))
.keySet();
}
public static void clearRequestAsProcessingSubRequest(RequestDetails theRequestDetails) {

View File

@ -58,6 +58,7 @@ import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.api.server.storage.DeferredInterceptorBroadcasts;
import ca.uhn.fhir.rest.api.server.storage.IResourcePersistentId;
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.RestfulServerUtils;
@ -1680,13 +1681,10 @@ public abstract class BaseTransactionProcessor {
if (newId != null) {
ourLog.debug(" * Replacing resource ref {} with {}", nextId, newId);
addRollbackReferenceRestore(theTransactionDetails, resourceReference);
if (theReferencesToAutoVersion.contains(resourceReference)) {
resourceReference.setReference(newId.getValue());
resourceReference.setResource(null);
replaceResourceReference(newId, resourceReference, theTransactionDetails);
} else {
resourceReference.setReference(newId.toVersionless().getValue());
resourceReference.setResource(null);
replaceResourceReference(newId.toVersionless(), resourceReference, theTransactionDetails);
}
}
} else if (nextId.getValue().startsWith("urn:")) {
@ -1724,9 +1722,15 @@ public abstract class BaseTransactionProcessor {
DaoMethodOutcome outcome = theIdToPersistedOutcome.get(nextId);
if (outcome != null && !outcome.isNop() && !Boolean.TRUE.equals(outcome.getCreated())) {
addRollbackReferenceRestore(theTransactionDetails, resourceReference);
resourceReference.setReference(nextId.getValue());
resourceReference.setResource(null);
replaceResourceReference(nextId, resourceReference, theTransactionDetails);
}
// if referenced resource is not in transaction but exists in the DB, resolving its version
IResourcePersistentId persistedReferenceId = resourceVersionMap.getResourcePersistentId(nextId);
if (outcome == null && persistedReferenceId != null && persistedReferenceId.getVersion() != null) {
IIdType newReferenceId = nextId.withVersion(
persistedReferenceId.getVersion().toString());
replaceResourceReference(newReferenceId, resourceReference, theTransactionDetails);
}
}
}
@ -1814,6 +1818,13 @@ public abstract class BaseTransactionProcessor {
}
}
private void replaceResourceReference(
IIdType theReferenceId, IBaseReference theResourceReference, TransactionDetails theTransactionDetails) {
addRollbackReferenceRestore(theTransactionDetails, theResourceReference);
theResourceReference.setReference(theReferenceId.getValue());
theResourceReference.setResource(null);
}
private void addRollbackReferenceRestore(
TransactionDetails theTransactionDetails, IBaseReference resourceReference) {
String existingValue = resourceReference.getReferenceElement().getValue();