From 8c287f02693c24d512e21713f02ffc0f1f6eb62b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Thu, 26 Jan 2023 09:55:19 -0500 Subject: [PATCH] Terser cloneInto doesn't work for contained resources (#4467) * Add fix * Add docs --- .../java/ca/uhn/fhir/util/FhirTerser.java | 21 +++++++++++-- .../4467-fix-clone-into-for-contained.yaml | 5 ++++ .../ca/uhn/fhir/util/FhirTerserDstu2Test.java | 30 +++++++++++++++++-- 3 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4467-fix-clone-into-for-contained.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java index b2f7384e81b..d708a929eda 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java @@ -197,7 +197,8 @@ public class FhirTerser { for (BaseRuntimeChildDefinition nextChild : children) for (IBase nextValue : nextChild.getAccessor().getValues(theSource)) { - String elementName = nextChild.getChildNameByDatatype(nextValue.getClass()); + Class valueType = nextValue.getClass(); + String elementName = nextChild.getChildNameByDatatype(valueType); BaseRuntimeChildDefinition targetChild = targetDef.getChildByName(elementName); if (targetChild == null) { if (theIgnoreMissingFields) { @@ -206,10 +207,24 @@ public class FhirTerser { throw new DataFormatException(Msg.code(1789) + "Type " + theTarget.getClass().getName() + " does not have a child with name " + elementName); } - BaseRuntimeElementDefinition element = myContext.getElementDefinition(nextValue.getClass()); + BaseRuntimeElementDefinition element = myContext.getElementDefinition(valueType); Object instanceConstructorArg = targetChild.getInstanceConstructorArguments(); IBase target; - if (instanceConstructorArg != null) { + if (element == null && BaseContainedDt.class.isAssignableFrom(valueType)) { + /* + * This is a hack for DSTU2 - The way we did contained resources in + * the DSTU2 model was weird, since the element isn't actually a FHIR type. + * This is fixed in DSTU3+ so this hack only applies there. + */ + BaseContainedDt containedTarget = (BaseContainedDt) ReflectionUtil.newInstance(valueType); + BaseContainedDt containedSource = (BaseContainedDt) nextValue; + for (IResource next : containedSource.getContainedResources()) { + List containedResources = containedTarget.getContainedResources(); + containedResources.add(next); + } + targetChild.getMutator().addValue(theTarget, containedTarget); + continue; + } else if (instanceConstructorArg != null) { target = element.newInstance(instanceConstructorArg); } else { target = element.newInstance(); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4467-fix-clone-into-for-contained.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4467-fix-clone-into-for-contained.yaml new file mode 100644 index 00000000000..62bec71eabc --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_4_0/4467-fix-clone-into-for-contained.yaml @@ -0,0 +1,5 @@ +--- +type: fix +issue: 4467 +title: "The FhirTerser#clone method failed to clone resources when contained resources were + present in the source resource. This has been fixed." diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/util/FhirTerserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/util/FhirTerserDstu2Test.java index 83c06088bd5..b7e149e007f 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/util/FhirTerserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/util/FhirTerserDstu2Test.java @@ -19,10 +19,13 @@ import ca.uhn.fhir.parser.DataFormatException; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseReference; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -30,6 +33,7 @@ import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -41,7 +45,8 @@ import static org.mockito.Mockito.when; public class FhirTerserDstu2Test { - private static FhirContext ourCtx = FhirContext.forDstu2(); + private static FhirContext ourCtx = FhirContext.forDstu2Cached(); + private static final Logger ourLog = LoggerFactory.getLogger(FhirTerserDstu2Test.class); @Test public void testCloneIntoComposite() { @@ -53,7 +58,28 @@ public class FhirTerserDstu2Test { assertEquals("CODE", target.getCode()); } - + + @Test + public void testCloneResource() { + Organization org = new Organization(); + org.setName("Contained Org Name"); + Patient patient = new Patient(); + patient.setActive(true); + patient.getManagingOrganization().setResource(org); + + // Re-encode + String string = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient); + ourLog.info("Encoded: {}", string); + patient = ourCtx.newJsonParser().parseResource(Patient.class, string); + + Patient clonedPatient = ourCtx.newTerser().clone(patient); + assertEquals(true, clonedPatient.getActive().booleanValue()); + string = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(clonedPatient); + ourLog.info("Encoded: {}", string); + assertThat(string, containsString("\"contained\"")); + } + + @Test public void testCloneIntoCompositeMismatchedFields() { QuantityDt source = new QuantityDt();