From 9b3224b2ba9da1d64aef21ba78fc4ee67baaad06 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 9 Jan 2024 17:12:11 -0600 Subject: [PATCH 1/4] Don't contain resources in Bundle with only fullUrl --- .../java/ca/uhn/fhir/parser/BaseParser.java | 28 ++++-- .../java/ca/uhn/fhir/parser/JsonParser.java | 2 +- .../java/ca/uhn/fhir/parser/RDFParser.java | 2 +- .../java/ca/uhn/fhir/parser/XmlParser.java | 2 +- .../java/ca/uhn/fhir/util/BundleUtil.java | 59 ++++++++++--- .../fhir/rest/server/RestfulServerUtils.java | 6 -- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 85 ++++++++++++++++++- .../ca/uhn/fhir/parser/RDFParserTest.java | 32 +++++++ .../ca/uhn/fhir/parser/XmlParserR4Test.java | 54 ++++++++++++ 9 files changed, 237 insertions(+), 33 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index a8a6e9a24eee..4e2fb0171715 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -49,6 +49,7 @@ import org.apache.commons.io.output.StringBuilderWriter; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseCoding; @@ -82,6 +83,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.apache.commons.lang3.StringUtils.startsWith; @SuppressWarnings("WeakerAccess") public abstract class BaseParser implements IParser { @@ -328,10 +330,7 @@ protected void encodeResourceToWriter(IBaseResource theResource, Writer theWrite Validate.notNull(theWriter, "theWriter can not be null"); Validate.notNull(theEncodeContext, "theEncodeContext can not be null"); - if (myContext.getVersion().getVersion() == FhirVersionEnum.R4B - && theResource.getStructureFhirVersionEnum() == FhirVersionEnum.R5) { - // TODO: remove once we've bumped the core lib version - } else if (theResource.getStructureFhirVersionEnum() + if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { throw new IllegalArgumentException(Msg.code(1829) + "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " @@ -444,10 +443,6 @@ FhirTerser.ContainedResources getContainedResources() { return myContainedResources; } - void setContainedResources(FhirTerser.ContainedResources theContainedResources) { - myContainedResources = theContainedResources; - } - @Override public Set getDontStripVersionsFromReferencesAtPaths() { return myDontStripVersionsFromReferencesAtPaths; @@ -1010,6 +1005,23 @@ protected boolean isFhirVersionLessThanOrEqualTo(FhirVersionEnum theFhirVersionE return theFhirVersionEnum == apiFhirVersion || apiFhirVersion.isOlderThan(theFhirVersionEnum); } + protected void containResourcesInReferences(IBaseResource theResource) { + if (theResource instanceof IBaseBundle) { + List> entries = BundleUtil.getBundleEntryFullUrlsAndResources(getContext(), (IBaseBundle) theResource); + for (Pair nextEntry : entries) { + String fullUrl = nextEntry.getKey(); + IBaseResource resource = nextEntry.getValue(); + if (startsWith(fullUrl, "urn:")) { + if (resource != null && resource.getIdElement().getValue() == null) { + resource.getIdElement().setValue(fullUrl); + } + } + } + } + + myContainedResources = getContext().newTerser().containResources(theResource); + } + class ChildNameAndDef { private final BaseRuntimeElementDefinition myChildDef; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 81cfc31fcf30..453b6c7606b4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -892,7 +892,7 @@ private void encodeResourceToJsonStreamWriter( } if (!theContainedResource) { - setContainedResources(getContext().newTerser().containResources(theResource)); + containResourcesInReferences(theResource); } RuntimeResourceDefinition resDef = getContext().getResourceDefinition(theResource); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java index ea7ded327352..acbcc4b28a8b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java @@ -191,7 +191,7 @@ private Resource encodeResourceToRDFStreamWriter( } if (!containedResource) { - setContainedResources(getContext().newTerser().containResources(resource)); + containResourcesInReferences(resource); } if (!(resource instanceof IAnyResource)) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 7e03b51c3619..a7f9257d7874 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -682,7 +682,7 @@ private void encodeResourceToXmlStreamWriter( } if (!theContainedResource) { - setContainedResources(getContext().newTerser().containResources(theResource)); + containResourcesInReferences(theResource); } theEventWriter.writeStartElement(resDef.getName()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java index 2b8692c65ba6..0eb95d46a3e1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java @@ -52,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -150,8 +151,15 @@ private static String getLinkNoCheck(FhirContext theContext, IBaseBundle theBund return getLinkUrlOfType(theContext, theBundle, theLinkRelation, false); } + /** + * Returns a collection of Pairs, one for each entry in the bundle. Each pair will contain + * the values of Bundle.entry.fullUrl, and Bundle.entry.resource respectively. Nulls + * are possible in either or both values in the Pair. + * + * @since 7.0.0 + */ @SuppressWarnings("unchecked") - public static List> getBundleEntryUrlsAndResources( + public static List> getBundleEntryFullUrlsAndResources( FhirContext theContext, IBaseBundle theBundle) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); @@ -161,28 +169,50 @@ public static List> getBundleEntryUrlsAndResources( (BaseRuntimeElementCompositeDefinition) entryChild.getChildByName("entry"); BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); + BaseRuntimeChildDefinition urlChild = entryChildElem.getChildByName("fullUrl"); + + List> retVal = new ArrayList<>(entries.size()); + for (IBase nextEntry : entries) { + + String fullUrl = urlChild.getAccessor().getFirstValueOrNull(nextEntry).map(t -> (((IPrimitiveType) t).getValueAsString())).orElse(null); + IBaseResource resource = (IBaseResource) resourceChild.getAccessor().getFirstValueOrNull(nextEntry).orElse(null); + + retVal.add(Pair.of(fullUrl, resource)); + } + + return retVal; + } + + public static List> getBundleEntryUrlsAndResources( + FhirContext theContext, IBaseBundle theBundle) { + RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); + BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); + List entries = entryChild.getAccessor().getValues(theBundle); + + BaseRuntimeElementCompositeDefinition entryChildElem = + (BaseRuntimeElementCompositeDefinition) entryChild.getChildByName("entry"); + BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); + BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request"); BaseRuntimeElementCompositeDefinition requestDef = - (BaseRuntimeElementCompositeDefinition) requestChild.getChildByName("request"); + (BaseRuntimeElementCompositeDefinition) requestChild.getChildByName("request"); BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url"); List> retVal = new ArrayList<>(entries.size()); for (IBase nextEntry : entries) { - String url = null; - IBaseResource resource = null; - - for (IBase nextEntryValue : requestChild.getAccessor().getValues(nextEntry)) { - for (IBase nextUrlValue : urlChild.getAccessor().getValues(nextEntryValue)) { - url = ((IPrimitiveType) nextUrlValue).getValue(); - } - } + String url = requestChild + .getAccessor() + .getFirstValueOrNull(nextEntry) + .flatMap(e -> urlChild.getAccessor().getFirstValueOrNull(e)) + .map(t -> ((IPrimitiveType) t).getValueAsString()) + .orElse(null); - // Should return 0..1 only - for (IBase nextValue : resourceChild.getAccessor().getValues(nextEntry)) { - resource = (IBaseResource) nextValue; - } + IBaseResource resource = (IBaseResource) resourceChild + .getAccessor() + .getFirstValueOrNull(nextEntry) + .orElse(null); retVal.add(Pair.of(url, resource)); } @@ -190,6 +220,7 @@ public static List> getBundleEntryUrlsAndResources( return retVal; } + public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); BaseRuntimeChildDefinition entryChild = def.getChildByName("type"); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 29824de5296b..a88e50327b69 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -682,12 +682,6 @@ public static IIdType fullyQualifyResourceIdOrReturnNull( } private static FhirContext getContextForVersion(FhirContext theContext, FhirVersionEnum theForVersion) { - - // TODO: remove once we've bumped the core lib version - if (theContext.getVersion().getVersion() == FhirVersionEnum.R4B && theForVersion == FhirVersionEnum.R5) { - return theContext; - } - FhirContext context = theContext; if (context.getVersion().getVersion() != theForVersion) { context = myFhirContextMap.get(theForVersion); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java index 6dd4e7893df6..47b73e6d0cc5 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java @@ -27,7 +27,6 @@ import org.hl7.fhir.r4.model.Encounter; import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.HumanName; -import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.Identifier; import org.hl7.fhir.r4.model.Medication; import org.hl7.fhir.r4.model.MedicationDispense; @@ -37,13 +36,13 @@ import org.hl7.fhir.r4.model.Narrative; import org.hl7.fhir.r4.model.Observation; import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.PrimitiveType; import org.hl7.fhir.r4.model.Quantity; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Type; import org.hl7.fhir.utilities.xhtml.XhtmlNode; @@ -54,6 +53,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.Date; @@ -1200,6 +1200,87 @@ public void testEncodeToString_BackboneElement() { assertEquals(expected, actual); } + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = createBundleWithCrossReferenceFullUrlsAndNoIds(); + + String output = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle); + ourLog.info(output); + + assertThat(output, not(containsString("\"contained\""))); + assertThat(output, not(containsString("\"id\""))); + assertThat(output, stringContainsInOrder( + "\"fullUrl\": \"urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7\",", + "\"resourceType\": \"Patient\"", + "\"fullUrl\": \"urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb\"", + "\"resourceType\": \"Observation\"", + "\"reference\": \"urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7\"" + )); + + } + + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters() { + Parameters parameters = createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters(); + + String output = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(parameters); + ourLog.info(output); + + assertThat(output, not(containsString("\"contained\""))); + assertThat(output, not(containsString("\"id\""))); + assertThat(output, stringContainsInOrder( + "\"resourceType\": \"Parameters\"", + "\"name\": \"resource\"", + "\"fullUrl\": \"urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7\",", + "\"resourceType\": \"Patient\"", + "\"fullUrl\": \"urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb\"", + "\"resourceType\": \"Observation\"", + "\"reference\": \"urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7\"" + )); + + } + + @Test + public void testParseBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = createBundleWithCrossReferenceFullUrlsAndNoIds(); + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(bundle); + + Bundle parsedBundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); + assertEquals("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7", parsedBundle.getEntry().get(0).getFullUrl()); + assertEquals("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7", parsedBundle.getEntry().get(0).getResource().getId()); + assertEquals("urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb", parsedBundle.getEntry().get(1).getFullUrl()); + assertEquals("urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb", parsedBundle.getEntry().get(1).getResource().getId()); + } + + @Nonnull + public static Bundle createBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = new Bundle(); + + Patient patient = new Patient(); + patient.setActive(true); + bundle + .addEntry() + .setResource(patient) + .setFullUrl("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7"); + + Observation observation = new Observation(); + observation.getSubject().setReference("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7").setResource(patient); + bundle + .addEntry() + .setResource(observation) + .setFullUrl("urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb"); + return bundle; + } + + @Nonnull + public static Parameters createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters() { + Parameters retVal = new Parameters(); + retVal + .addParameter() + .setName("resource") + .setResource(createBundleWithCrossReferenceFullUrlsAndNoIds()); + return retVal; + } @Test public void testPreCommentsToFhirComments() { diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java index 7ddda19896aa..f33d241b4cb4 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserTest.java @@ -36,8 +36,11 @@ import org.eclipse.rdf4j.rio.Rio; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.Base; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Parameters; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.params.ParameterizedTest; @@ -59,6 +62,12 @@ import java.util.Optional; import java.util.stream.Stream; +import static ca.uhn.fhir.parser.JsonParserR4Test.createBundleWithCrossReferenceFullUrlsAndNoIds; +import static ca.uhn.fhir.parser.JsonParserR4Test.createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.hamcrest.core.IsNot.not; import static org.junit.jupiter.api.Assertions.*; public class RDFParserTest extends BaseTest { @@ -216,4 +225,27 @@ public String toString() { } } + + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = createBundleWithCrossReferenceFullUrlsAndNoIds(); + + String output = ourCtx.newRDFParser().setPrettyPrint(true).encodeResourceToString(bundle); + ourLog.info(output); + + assertThat(output, not(containsString("contained "))); + assertThat(output, not(containsString("id "))); + } + + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters() { + Parameters parameters = createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters(); + + String output = ourCtx.newRDFParser().setPrettyPrint(true).encodeResourceToString(parameters); + ourLog.info(output); + + assertThat(output, not(containsString("contained "))); + assertThat(output, not(containsString("id "))); + } + } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java index 16af55d7ac63..7d4f4dc90928 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java @@ -1,8 +1,11 @@ package ca.uhn.fhir.parser; +import static ca.uhn.fhir.parser.JsonParserR4Test.createBundleWithCrossReferenceFullUrlsAndNoIds; +import static ca.uhn.fhir.parser.JsonParserR4Test.createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.hamcrest.core.IsNot.not; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -304,5 +307,56 @@ public void testDivXhtmlLangAttribute() { } + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = createBundleWithCrossReferenceFullUrlsAndNoIds(); + + String output = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle); + ourLog.info(output); + + assertThat(output, not(containsString("", + "", + "", + "", + "" + )); + + } + + @Test + public void testEncodeBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters() { + Parameters parameters = createBundleWithCrossReferenceFullUrlsAndNoIds_NestedInParameters(); + + String output = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parameters); + ourLog.info(output); + + assertThat(output, not(containsString("\"contained\""))); + assertThat(output, not(containsString("\"id\""))); + assertThat(output, stringContainsInOrder( + "", + "", + "", + "", + "", + "" + )); + + } + + @Test + public void testParseBundleWithCrossReferenceFullUrlsAndNoIds() { + Bundle bundle = createBundleWithCrossReferenceFullUrlsAndNoIds(); + String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(bundle); + + Bundle parsedBundle = ourCtx.newXmlParser().parseResource(Bundle.class, encoded); + assertEquals("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7", parsedBundle.getEntry().get(0).getFullUrl()); + assertEquals("urn:uuid:9e9187c1-db6d-4b6f-adc6-976153c65ed7", parsedBundle.getEntry().get(0).getResource().getId()); + assertEquals("urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb", parsedBundle.getEntry().get(1).getFullUrl()); + assertEquals("urn:uuid:71d7ab79-a001-41dc-9a8e-b3e478ce1cbb", parsedBundle.getEntry().get(1).getResource().getId()); + } + } From fa3aeba5e34030469ebb6ba25401283d0507ee33 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 9 Jan 2024 17:17:49 -0600 Subject: [PATCH 2/4] Add changelog --- .../src/main/java/ca/uhn/fhir/parser/BaseParser.java | 8 ++++++++ .../7_0_0/5589-dont-contain-resources-with-fullurl.yaml | 7 +++++++ 2 files changed, 15 insertions(+) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-contain-resources-with-fullurl.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 4e2fb0171715..1b0864a16d21 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -1006,6 +1006,14 @@ protected boolean isFhirVersionLessThanOrEqualTo(FhirVersionEnum theFhirVersionE } protected void containResourcesInReferences(IBaseResource theResource) { + + /* + * If a UUID is present in Bundle.entry.fullUrl but no value is present + * in Bundle.entry.resource.id, the resource has a discrete identity which + * should be copied into the resource ID. It will not be serialized in + * Resource.id because it's a placeholder/UUID value, but its presence there + * informs the serializer that we don't need to contain this resource. + */ if (theResource instanceof IBaseBundle) { List> entries = BundleUtil.getBundleEntryFullUrlsAndResources(getContext(), (IBaseBundle) theResource); for (Pair nextEntry : entries) { diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-contain-resources-with-fullurl.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-contain-resources-with-fullurl.yaml new file mode 100644 index 000000000000..45c8d424e904 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-contain-resources-with-fullurl.yaml @@ -0,0 +1,7 @@ +--- +type: fix +issue: 5589 +title: "When encoding a Bundle, if resources in bundle entries had a value in + `Bundle.entry.fullUrl` but no value in `Bundle.entry.resource.id`, the parser + sometimes incorrectly moved these resources to be contained within other + resources when serializing the bundle. This has been corrected." From c8b10869b23f026db88cb0ec4270e8447efdd86b Mon Sep 17 00:00:00 2001 From: James Agnew Date: Tue, 9 Jan 2024 17:18:14 -0600 Subject: [PATCH 3/4] Spotless --- .../java/ca/uhn/fhir/parser/BaseParser.java | 8 ++--- .../java/ca/uhn/fhir/util/BundleUtil.java | 34 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 1b0864a16d21..7b8c615b708f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -330,8 +330,7 @@ protected void encodeResourceToWriter(IBaseResource theResource, Writer theWrite Validate.notNull(theWriter, "theWriter can not be null"); Validate.notNull(theEncodeContext, "theEncodeContext can not be null"); - if (theResource.getStructureFhirVersionEnum() - != myContext.getVersion().getVersion()) { + if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { throw new IllegalArgumentException(Msg.code(1829) + "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); @@ -1015,7 +1014,8 @@ protected void containResourcesInReferences(IBaseResource theResource) { * informs the serializer that we don't need to contain this resource. */ if (theResource instanceof IBaseBundle) { - List> entries = BundleUtil.getBundleEntryFullUrlsAndResources(getContext(), (IBaseBundle) theResource); + List> entries = + BundleUtil.getBundleEntryFullUrlsAndResources(getContext(), (IBaseBundle) theResource); for (Pair nextEntry : entries) { String fullUrl = nextEntry.getKey(); IBaseResource resource = nextEntry.getValue(); @@ -1027,7 +1027,7 @@ protected void containResourcesInReferences(IBaseResource theResource) { } } - myContainedResources = getContext().newTerser().containResources(theResource); + myContainedResources = getContext().newTerser().containResources(theResource); } class ChildNameAndDef { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java index 0eb95d46a3e1..ccac7644f141 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/BundleUtil.java @@ -52,7 +52,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -174,8 +173,12 @@ public static List> getBundleEntryFullUrlsAndResourc List> retVal = new ArrayList<>(entries.size()); for (IBase nextEntry : entries) { - String fullUrl = urlChild.getAccessor().getFirstValueOrNull(nextEntry).map(t -> (((IPrimitiveType) t).getValueAsString())).orElse(null); - IBaseResource resource = (IBaseResource) resourceChild.getAccessor().getFirstValueOrNull(nextEntry).orElse(null); + String fullUrl = urlChild.getAccessor() + .getFirstValueOrNull(nextEntry) + .map(t -> (((IPrimitiveType) t).getValueAsString())) + .orElse(null); + IBaseResource resource = (IBaseResource) + resourceChild.getAccessor().getFirstValueOrNull(nextEntry).orElse(null); retVal.add(Pair.of(fullUrl, resource)); } @@ -184,35 +187,33 @@ public static List> getBundleEntryFullUrlsAndResourc } public static List> getBundleEntryUrlsAndResources( - FhirContext theContext, IBaseBundle theBundle) { + FhirContext theContext, IBaseBundle theBundle) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); BaseRuntimeChildDefinition entryChild = def.getChildByName("entry"); List entries = entryChild.getAccessor().getValues(theBundle); BaseRuntimeElementCompositeDefinition entryChildElem = - (BaseRuntimeElementCompositeDefinition) entryChild.getChildByName("entry"); + (BaseRuntimeElementCompositeDefinition) entryChild.getChildByName("entry"); BaseRuntimeChildDefinition resourceChild = entryChildElem.getChildByName("resource"); BaseRuntimeChildDefinition requestChild = entryChildElem.getChildByName("request"); BaseRuntimeElementCompositeDefinition requestDef = - (BaseRuntimeElementCompositeDefinition) requestChild.getChildByName("request"); + (BaseRuntimeElementCompositeDefinition) requestChild.getChildByName("request"); BaseRuntimeChildDefinition urlChild = requestDef.getChildByName("url"); List> retVal = new ArrayList<>(entries.size()); for (IBase nextEntry : entries) { - String url = requestChild - .getAccessor() - .getFirstValueOrNull(nextEntry) - .flatMap(e -> urlChild.getAccessor().getFirstValueOrNull(e)) - .map(t -> ((IPrimitiveType) t).getValueAsString()) - .orElse(null); + String url = requestChild + .getAccessor() + .getFirstValueOrNull(nextEntry) + .flatMap(e -> urlChild.getAccessor().getFirstValueOrNull(e)) + .map(t -> ((IPrimitiveType) t).getValueAsString()) + .orElse(null); - IBaseResource resource = (IBaseResource) resourceChild - .getAccessor() - .getFirstValueOrNull(nextEntry) - .orElse(null); + IBaseResource resource = (IBaseResource) + resourceChild.getAccessor().getFirstValueOrNull(nextEntry).orElse(null); retVal.add(Pair.of(url, resource)); } @@ -220,7 +221,6 @@ public static List> getBundleEntryUrlsAndResources( return retVal; } - public static String getBundleType(FhirContext theContext, IBaseBundle theBundle) { RuntimeResourceDefinition def = theContext.getResourceDefinition(theBundle); BaseRuntimeChildDefinition entryChild = def.getChildByName("type"); From 0106c31de222bcb8f09781179d3f24ed46e3f217 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 10 Jan 2024 06:24:30 -0700 Subject: [PATCH 4/4] Fix RDF issue --- .../src/main/java/ca/uhn/fhir/parser/RDFParser.java | 4 +++- .../7_0_0/5589-dont-encode-placeholder-ids-in-rdf.yaml | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-encode-placeholder-ids-in-rdf.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java index acbcc4b28a8b..05a3e13c3d4c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java @@ -238,7 +238,9 @@ private Resource encodeResourceToRDFStreamWriter( rdfModel.createProperty(FHIR_NS + NODE_ROLE), rdfModel.createProperty(FHIR_NS + TREE_ROOT)); } - if (resourceId != null && resourceId.getIdPart() != null) { + if (resourceId != null + && resourceId.getIdPart() != null + && !resourceId.getValue().startsWith("urn:")) { parentResource.addProperty( rdfModel.createProperty(FHIR_NS + RESOURCE_ID), createFhirValueBlankNode(rdfModel, resourceId.getIdPart())); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-encode-placeholder-ids-in-rdf.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-encode-placeholder-ids-in-rdf.yaml new file mode 100644 index 000000000000..28bf0b687a3a --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_0_0/5589-dont-encode-placeholder-ids-in-rdf.yaml @@ -0,0 +1,6 @@ +--- +type: fix +issue: 5589 +title: "When encoding resources using the RDF parser, placeholder IDs (i.e. resource IDs + starting with `urn:`) were not omitted as they are in the XML and JSON parsers. This has + been corrected."