From 9e9f1e65e2534a7ece1574fa0529d813fbc6392b Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 26 Nov 2024 10:33:37 -0500 Subject: [PATCH 01/29] Breaking test --- .../java/ca/uhn/fhir/parser/JsonParserR4Test.java | 9 +++++++++ .../observation-with-contained-specimen.json | 14 ++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json 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 b80251d29108..dc4f6a96e635 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 @@ -1379,6 +1379,15 @@ public void testPreCommentsToFhirComments() { assertThat(patientString).doesNotContain("fhir_comment"); } + @Test + public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException { + String text = loadResource("/observation-with-contained-specimen.json"); + + Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); + assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + @DatatypeDef( name = "UnknownPrimitiveType" ) diff --git a/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json new file mode 100644 index 000000000000..49a8153cfc71 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Observation", + "id": "O1", + "contained": [ + { + "resourceType": "Specimen", + "id": "contained-id" + } + ], + "status": "final", + "specimen": { + "reference": "#contained-id" + } +} From 7a35d49980b8668f3dd9edcd1deec55b978b2e8f Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 26 Nov 2024 18:01:42 -0500 Subject: [PATCH 02/29] Additional testing to demonstrate mutations --- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 110 ++++++++++++++++++ ...ation-with-contained-specimen-hash-id.json | 14 +++ 2 files changed, 124 insertions(+) create mode 100644 hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json 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 dc4f6a96e635..3bbf5bd1ea7c 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 @@ -46,6 +46,7 @@ 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.Specimen; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Type; @@ -1385,9 +1386,118 @@ public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); + //FIXME the parsed resource has an added `#` in the contained resource id. assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); } + @Test + public void testContainedResourceIdIsReadWithoutAddingHashThatAlreadyExists() throws IOException { + String text = loadResource("/observation-with-contained-specimen-hash-id.json"); + + Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); + // FIXME? This shouldn't be a valid resource, but it's loaded and made into `##contained-id` anyway. + assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); + assertThat(observation.getContained().get(0).getId()).isEqualTo("#contained-id"); + } + + @Test + public void testReferenceCreatedByStringDoesntMutateContained() { + Observation observation = new Observation(); + observation.setId("123"); + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference("#contained-id")); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); + + assertThat(observation.getContained().size()).isEqualTo(1); + /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given + an id prefixed with a hash. + */ + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + + @Test + public void testReferenceCreatedByResourceDoesntMutateContained() { + + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + + Observation observation = new Observation(); + observation.setId("123"); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference(specimen)); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); + + assertThat(observation.getContained().size()).isEqualTo(1); + /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given + an id prefixed with a hash. + */ + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } + + @Test + public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { + Specimen specimen = new Specimen(); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.setSpecimen(new Reference(specimen)); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + // When encoding, if the reference does not have an id, it is generated in the FhirTerser + String specimenReferenceId = observation.getSpecimen().getResource().getIdElement().getValue(); + assertThat(specimenReferenceId).startsWith("#"); + + // the terser doesn't add a new contained element to the observation + assertThat(observation.getContained()).isEmpty(); + + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + + } + + + @Test + public void testReferenceCreatedByIdTypeDoesntMutateContained() { + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference(new IdType("#contained-id"))); + + String text = ourCtx.newJsonParser().encodeResourceToString(observation); + + // When encoding, + String specimenReferenceId = observation.getSpecimen().getReference(); + assertThat(specimenReferenceId).startsWith("#"); + + // FIXME why does the terser mutate the contained element of the original resource? + String specimenContainedId = observation.getContained().get(0).getIdElement().getValue(); + assertThat(specimenContainedId).isEqualTo("contained-id"); + + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + + } + @DatatypeDef( name = "UnknownPrimitiveType" ) diff --git a/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json new file mode 100644 index 000000000000..350ef5606790 --- /dev/null +++ b/hapi-fhir-structures-r4/src/test/resources/observation-with-contained-specimen-hash-id.json @@ -0,0 +1,14 @@ +{ + "resourceType": "Observation", + "id": "O1", + "contained": [ + { + "resourceType": "Specimen", + "id": "#contained-id" + } + ], + "status": "final", + "specimen": { + "reference": "#contained-id" + } +} From 4dd77607b7e1041c6c377d6f85446982f27360ae Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 15:51:40 -0500 Subject: [PATCH 03/29] Remove test - parser always produces valid FHIR contained resource ids --- .../test/java/ca/uhn/fhir/parser/JsonParserR4Test.java | 10 ---------- 1 file changed, 10 deletions(-) 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 5f8500c6f8e2..a206769461e0 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 @@ -1888,16 +1888,6 @@ public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); } - @Test - public void testContainedResourceIdIsReadWithoutAddingHashThatAlreadyExists() throws IOException { - String text = loadResource("/observation-with-contained-specimen-hash-id.json"); - - Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); - // FIXME? This shouldn't be a valid resource, but it's loaded and made into `##contained-id` anyway. - assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); - assertThat(observation.getContained().get(0).getId()).isEqualTo("#contained-id"); - } - @Test public void testReferenceCreatedByStringDoesntMutateContained() { Observation observation = new Observation(); From 7826faebf9f781635922a6cfe998bb8ac4dbca97 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 15:56:03 -0500 Subject: [PATCH 04/29] Move all contained tests to nested test --- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 771 +++++++++--------- 1 file changed, 394 insertions(+), 377 deletions(-) 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 a206769461e0..1d0e02adbf34 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 @@ -30,6 +30,7 @@ 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; @@ -56,6 +57,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -745,159 +748,8 @@ public void testPrettyPrint() { assertThat(output).contains("\n \"resourceType\""); } - /** - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwice() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - md.setMedication(new Reference(med)); - mr.setMedication(new Reference(med)); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - } - - @Test - public void testDuplicateContainedResourcesAcrossABundleAreReplicated() { - Bundle b = new Bundle(); - Specimen specimen = new Specimen(); - Practitioner practitioner = new Practitioner(); - DiagnosticReport report = new DiagnosticReport(); - report.addSpecimen(new Reference(specimen)); - b.addEntry().setResource(report).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/DiagnosticReport"); - Observation obs = new Observation(); - obs.addPerformer(new Reference(practitioner)); - obs.setSpecimen(new Reference(specimen)); - - b.addEntry().setResource(obs).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/Observation"); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b); - //Then: Diag should contain one local contained specimen - assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"}]"); - //Then: Obs should contain one local contained specimen, and one local contained pract - assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId().replaceAll("#","") + "\"}]"); - assertThat(encoded).contains("\"performer\":[{\"reference\":\""+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\""+specimen.getId()+"\"}"); - - //Also, reverting the operation should work too! - Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); - IBaseResource resource1 = ((DiagnosticReport) bundle.getEntry().get(0).getResource()).getSpecimenFirstRep().getResource(); - IBaseResource resource = ((Observation) bundle.getEntry().get(1).getResource()).getSpecimen().getResource(); - assertThat(resource1.getIdElement().getIdPart()).isEqualTo(resource.getIdElement().getIdPart()); - assertThat(resource1).isNotSameAs(resource); - - } - - @Test - public void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { - MedicationDispense md = new MedicationDispense(); - md.addIdentifier().setValue("DISPENSE"); - - Medication med = new Medication(); - med.getCode().setText("MED"); - md.setMedication(new Reference(med)); - ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(false); - String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); - assertEquals("{\"resourceType\":\"MedicationDispense\",\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{}}", encoded); - - ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true); - encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); - String guidWithHash = med.getId(); - String withoutHash = guidWithHash.replace("#", ""); - assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + withoutHash + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + guidWithHash +"\"}}"); //Note we dont check exact ID since its a GUID - } - - @Test - public void testParseBundleWithMultipleNestedContainedResources() throws Exception { - String text = loadResource("/bundle-with-two-patient-resources.json"); - - Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text); - assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); - - assertEquals("12346", getPatientIdValue(bundle, 0)); - assertEquals("12345", getPatientIdValue(bundle, 1)); - } - - private String getPatientIdValue(Bundle input, int entry) { - final DocumentReference documentReference = (DocumentReference) input.getEntry().get(entry).getResource(); - final Patient patient = (Patient) documentReference.getSubject().getResource(); - return patient.getIdentifier().get(0).getValue(); - } - - /** - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIds() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - mr.setId("#MR"); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - med.setId("#MED"); - md.setMedication(new Reference(med)); - mr.setMedication(new Reference(med)); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - - } - - /* - * See #814 - */ - @Test - public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIdsAndManualAddition() { - MedicationDispense md = new MedicationDispense(); - - MedicationRequest mr = new MedicationRequest(); - mr.setId("#MR"); - md.addAuthorizingPrescription().setResource(mr); - - Medication med = new Medication(); - med.setId("#MED"); - - Reference medRef = new Reference(); - medRef.setReference("#MED"); - md.setMedication(medRef); - mr.setMedication(medRef); - - md.getContained().add(mr); - md.getContained().add(med); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertThat(idx).isNotEqualTo(-1); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - - } /** * Make sure we can perform parallel parse/encodes against the same @@ -1088,85 +940,8 @@ public void testEncodeWithInvalidExtensionContainingValueAndNestedExtensions_wit assertEquals(false, errorHandler.isErrorOnInvalidValue()); } - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalFirst() { - - Observation obs = new Observation(); - - Patient pt = new Patient(); - pt.setId("#1"); - pt.addName().setFamily("FAM"); - obs.getSubject().setReference("#1"); - obs.getContained().add(pt); - - Encounter enc = new Encounter(); - enc.setStatus(Encounter.EncounterStatus.ARRIVED); - obs.getEncounter().setResource(enc); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals(enc.getId(), obs.getContained().get(1).getId()); - - pt = (Patient) obs.getSubject().getResource(); - assertEquals("FAM", pt.getNameFirstRep().getFamily()); - - enc = (Encounter) obs.getEncounter().getResource(); - assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); - } - - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast() { - - Observation obs = new Observation(); - - Patient pt = new Patient(); - pt.addName().setFamily("FAM"); - obs.getSubject().setResource(pt); - - Encounter enc = new Encounter(); - enc.setId("#1"); - enc.setStatus(Encounter.EncounterStatus.ARRIVED); - obs.getEncounter().setReference("#1"); - obs.getContained().add(enc); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals(pt.getId(), obs.getContained().get(1).getId()); - - pt = (Patient) obs.getSubject().getResource(); - assertEquals("FAM", pt.getNameFirstRep().getFamily()); - - enc = (Encounter) obs.getEncounter().getResource(); - assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); - } - - @Test - public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast2() { - - MedicationRequest mr = new MedicationRequest(); - Practitioner pract = new Practitioner().setActive(true); - mr.getRequester().setResource(pract); - - String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); - - Medication med = new Medication().setStatus(Medication.MedicationStatus.ACTIVE); - mr.setMedication(new Reference(med)); - encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); - assertEquals(pract.getId(), mr.getContained().get(0).getId()); - assertEquals(med.getId(), mr.getContained().get(1).getId()); - } @Test public void testExcludeNothing() { @@ -1429,7 +1204,12 @@ public void testEncodingTextSection() { assertThat(idx).isNotEqualTo(-1); } - + /** + * Enable individually if needed to test timings. + */ + @Nested + @DisplayName("Timing Tests") + class TimingTests { /** * 2019-09-19 - Pre #1489 * 18:24:48.548 [main] INFO ca.uhn.fhir.parser.JsonParserR4Test [JsonParserR4Test.java:483] - Encoded 200 passes - 50ms / pass - 19.7 / second @@ -1607,85 +1387,7 @@ private Bundle createBigBundle() { } return b; } - - /** - * Ensure that a contained bundle doesn't cause a crash - */ - @Test - public void testEncodeContainedBundle() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Bundle\",\n" + - " \"id\": \"REASONS\",\n" + - " \"entry\": [ {\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Condition\",\n" + - " \"id\": \"123\"\n" + - " }\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"MeasureReport\",\n" + - " \"id\": \"MRPT5000602611RD\",\n" + - " \"evaluatedResource\": [ {\n" + - " \"reference\": \"#REASONS\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#MRPT5000602611RD\"\n" + - " }\n" + - " } ]\n" + - "}"; - AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); - String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); - assertEquals(auditEvent, auditEventAsString); - } - - - /** - * Ensure that a contained bundle doesn't cause a crash - */ - @Test - public void testParseAndEncodePreservesContainedResourceOrder() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"A\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"A\"\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"B\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"B\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#B\"\n" + - " }\n" + - " }, {\n" + - " \"what\": {\n" + - " \"reference\": \"#A\"\n" + - " }\n" + - " } ]\n" + - "}"; - - ourLog.info("Input: {}", auditEvent); - AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); - assertEquals("#A", ae.getContained().get(0).getId()); - assertEquals("#B", ae.getContained().get(1).getId()); - assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); - assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); - - String serialized = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); - assertEquals(auditEvent, serialized); - - } - +} @Test public void testEncodeToString_PrimitiveDataType() { @@ -1878,114 +1580,429 @@ public void testPreCommentsToFhirComments() { assertThat(patientString).doesNotContain("fhir_comment"); } - @Test - public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException { + @Nested + @DisplayName("Contained resources handling") + class ContainedResources { + /** + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwice() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + md.setMedication(new Reference(med)); + mr.setMedication(new Reference(med)); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + } + + @Test + public void testDuplicateContainedResourcesAcrossABundleAreReplicated() { + Bundle b = new Bundle(); + Specimen specimen = new Specimen(); + Practitioner practitioner = new Practitioner(); + DiagnosticReport report = new DiagnosticReport(); + report.addSpecimen(new Reference(specimen)); + b.addEntry().setResource(report).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/DiagnosticReport"); + + Observation obs = new Observation(); + obs.addPerformer(new Reference(practitioner)); + obs.setSpecimen(new Reference(specimen)); + + b.addEntry().setResource(obs).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("/Observation"); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b); + //Then: Diag should contain one local contained specimen + assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"}]"); + //Then: Obs should contain one local contained specimen, and one local contained pract + assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId().replaceAll("#","") + "\"}]"); + assertThat(encoded).contains("\"performer\":[{\"reference\":\""+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\""+specimen.getId()+"\"}"); + + //Also, reverting the operation should work too! + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); + IBaseResource resource1 = ((DiagnosticReport) bundle.getEntry().get(0).getResource()).getSpecimenFirstRep().getResource(); + IBaseResource resource = ((Observation) bundle.getEntry().get(1).getResource()).getSpecimen().getResource(); + assertThat(resource1.getIdElement().getIdPart()).isEqualTo(resource.getIdElement().getIdPart()); + assertThat(resource1).isNotSameAs(resource); + + } + + @Test + public void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { + MedicationDispense md = new MedicationDispense(); + md.addIdentifier().setValue("DISPENSE"); + + Medication med = new Medication(); + med.getCode().setText("MED"); + md.setMedication(new Reference(med)); + + ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(false); + String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); + assertEquals("{\"resourceType\":\"MedicationDispense\",\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{}}", encoded); + + ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true); + encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); + String guidWithHash = med.getId(); + String withoutHash = guidWithHash.replace("#", ""); + assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + withoutHash + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + guidWithHash +"\"}}"); //Note we dont check exact ID since its a GUID + } + + @Test + public void testParseBundleWithMultipleNestedContainedResources() throws Exception { + String text = loadResource("/bundle-with-two-patient-resources.json"); + + Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text); + assertEquals(Boolean.TRUE, bundle.getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(0).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + assertEquals(Boolean.TRUE, bundle.getEntry().get(1).getResource().getUserData(BaseParser.RESOURCE_CREATED_BY_PARSER)); + + assertEquals("12346", getPatientIdValue(bundle, 0)); + assertEquals("12345", getPatientIdValue(bundle, 1)); + } + + private String getPatientIdValue(Bundle input, int entry) { + final DocumentReference documentReference = (DocumentReference) input.getEntry().get(entry).getResource(); + final Patient patient = (Patient) documentReference.getSubject().getResource(); + return patient.getIdentifier().get(0).getValue(); + } + /** + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIds() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + mr.setId("#MR"); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + med.setId("#MED"); + md.setMedication(new Reference(med)); + mr.setMedication(new Reference(med)); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + + } + /* + * See #814 + */ + @Test + public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIdsAndManualAddition() { + MedicationDispense md = new MedicationDispense(); + + MedicationRequest mr = new MedicationRequest(); + mr.setId("#MR"); + md.addAuthorizingPrescription().setResource(mr); + + Medication med = new Medication(); + med.setId("#MED"); + + Reference medRef = new Reference(); + medRef.setReference("#MED"); + md.setMedication(medRef); + mr.setMedication(medRef); + + md.getContained().add(mr); + md.getContained().add(med); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(md); + ourLog.info(encoded); + + int idx = encoded.indexOf("\"Medication\""); + assertThat(idx).isNotEqualTo(-1); + + idx = encoded.indexOf("\"Medication\"", idx + 1); + assertEquals(-1, idx); + + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalFirst() { + + Observation obs = new Observation(); + + Patient pt = new Patient(); + pt.setId("#1"); + pt.addName().setFamily("FAM"); + obs.getSubject().setReference("#1"); + obs.getContained().add(pt); + + Encounter enc = new Encounter(); + enc.setStatus(Encounter.EncounterStatus.ARRIVED); + obs.getEncounter().setResource(enc); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); + ourLog.info(encoded); + + obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); + assertEquals("#1", obs.getContained().get(0).getId()); + assertEquals(enc.getId(), obs.getContained().get(1).getId()); + + pt = (Patient) obs.getSubject().getResource(); + assertEquals("FAM", pt.getNameFirstRep().getFamily()); + + enc = (Encounter) obs.getEncounter().getResource(); + assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast() { + + Observation obs = new Observation(); + + Patient pt = new Patient(); + pt.addName().setFamily("FAM"); + obs.getSubject().setResource(pt); + + Encounter enc = new Encounter(); + enc.setId("#1"); + enc.setStatus(Encounter.EncounterStatus.ARRIVED); + obs.getEncounter().setReference("#1"); + obs.getContained().add(enc); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(obs); + ourLog.info(encoded); + + obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); + assertEquals("#1", obs.getContained().get(0).getId()); + assertEquals(pt.getId(), obs.getContained().get(1).getId()); + + pt = (Patient) obs.getSubject().getResource(); + assertEquals("FAM", pt.getNameFirstRep().getFamily()); + + enc = (Encounter) obs.getEncounter().getResource(); + assertEquals(Encounter.EncounterStatus.ARRIVED, enc.getStatus()); + } + + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testEncodeContainedBundle() { + String auditEvent = "{\n" + + " \"resourceType\": \"AuditEvent\",\n" + + " \"contained\": [ {\n" + + " \"resourceType\": \"Bundle\",\n" + + " \"id\": \"REASONS\",\n" + + " \"entry\": [ {\n" + + " \"resource\": {\n" + + " \"resourceType\": \"Condition\",\n" + + " \"id\": \"123\"\n" + + " }\n" + + " } ]\n" + + " }, {\n" + + " \"resourceType\": \"MeasureReport\",\n" + + " \"id\": \"MRPT5000602611RD\",\n" + + " \"evaluatedResource\": [ {\n" + + " \"reference\": \"#REASONS\"\n" + + " } ]\n" + + " } ],\n" + + " \"entity\": [ {\n" + + " \"what\": {\n" + + " \"reference\": \"#MRPT5000602611RD\"\n" + + " }\n" + + " } ]\n" + + "}"; + AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); + String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, auditEventAsString); + } + + + /** + * Ensure that a contained bundle doesn't cause a crash + */ + @Test + public void testParseAndEncodePreservesContainedResourceOrder() { + String auditEvent = "{\n" + + " \"resourceType\": \"AuditEvent\",\n" + + " \"contained\": [ {\n" + + " \"resourceType\": \"Observation\",\n" + + " \"id\": \"A\",\n" + + " \"identifier\": [ {\n" + + " \"value\": \"A\"\n" + + " } ]\n" + + " }, {\n" + + " \"resourceType\": \"Observation\",\n" + + " \"id\": \"B\",\n" + + " \"identifier\": [ {\n" + + " \"value\": \"B\"\n" + + " } ]\n" + + " } ],\n" + + " \"entity\": [ {\n" + + " \"what\": {\n" + + " \"reference\": \"#B\"\n" + + " }\n" + + " }, {\n" + + " \"what\": {\n" + + " \"reference\": \"#A\"\n" + + " }\n" + + " } ]\n" + + "}"; + + ourLog.info("Input: {}", auditEvent); + AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); + assertEquals("#A", ae.getContained().get(0).getId()); + assertEquals("#B", ae.getContained().get(1).getId()); + assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); + assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); + + String serialized = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); + assertEquals(auditEvent, serialized); + + } + + @Test + public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocalLast2() { + + MedicationRequest mr = new MedicationRequest(); + Practitioner pract = new Practitioner().setActive(true); + mr.getRequester().setResource(pract); + + String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); + + Medication med = new Medication().setStatus(Medication.MedicationStatus.ACTIVE); + mr.setMedication(new Reference(med)); + encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(mr); + ourLog.info(encoded); + mr = ourCtx.newJsonParser().parseResource(MedicationRequest.class, encoded); + + assertEquals(pract.getId(), mr.getContained().get(0).getId()); + assertEquals(med.getId(), mr.getContained().get(1).getId()); + + } + + @Test + public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException { String text = loadResource("/observation-with-contained-specimen.json"); Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); //FIXME the parsed resource has an added `#` in the contained resource id. assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); - } + } - @Test - public void testReferenceCreatedByStringDoesntMutateContained() { - Observation observation = new Observation(); - observation.setId("123"); - Specimen specimen = new Specimen(); - specimen.setId("contained-id"); - observation.getContained().add(specimen); - observation.setSpecimen(new Reference("#contained-id")); + @Test + public void testReferenceCreatedByStringDoesntMutateContained() { + Observation observation = new Observation(); + observation.setId("123"); + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference("#contained-id")); - String text = ourCtx.newJsonParser().encodeResourceToString(observation); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); - assertThat(text).contains("\"reference\":\"#contained-id\""); - assertThat(text).contains("\"id\":\"contained-id\""); + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); - assertThat(observation.getContained().size()).isEqualTo(1); + assertThat(observation.getContained().size()).isEqualTo(1); /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given an id prefixed with a hash. */ - assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); - } + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } - @Test - public void testReferenceCreatedByResourceDoesntMutateContained() { + @Test + public void testReferenceCreatedByResourceDoesntMutateContained() { - Specimen specimen = new Specimen(); - specimen.setId("contained-id"); + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); - Observation observation = new Observation(); - observation.setId("123"); - observation.getContained().add(specimen); - observation.setSpecimen(new Reference(specimen)); + Observation observation = new Observation(); + observation.setId("123"); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference(specimen)); - String text = ourCtx.newJsonParser().encodeResourceToString(observation); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); - assertThat(text).contains("\"reference\":\"#contained-id\""); - assertThat(text).contains("\"id\":\"contained-id\""); + assertThat(text).contains("\"reference\":\"#contained-id\""); + assertThat(text).contains("\"id\":\"contained-id\""); - assertThat(observation.getContained().size()).isEqualTo(1); + assertThat(observation.getContained().size()).isEqualTo(1); /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given an id prefixed with a hash. */ - assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); - } + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); + } - @Test - public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { - Specimen specimen = new Specimen(); - specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + @Test + public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { + Specimen specimen = new Specimen(); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); - Observation observation = new Observation(); - observation.setId("O1"); - observation.setStatus(Observation.ObservationStatus.FINAL); - observation.setSpecimen(new Reference(specimen)); + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.setSpecimen(new Reference(specimen)); - String text = ourCtx.newJsonParser().encodeResourceToString(observation); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); - // When encoding, if the reference does not have an id, it is generated in the FhirTerser - String specimenReferenceId = observation.getSpecimen().getResource().getIdElement().getValue(); - assertThat(specimenReferenceId).startsWith("#"); + // When encoding, if the reference does not have an id, it is generated in the FhirTerser + String specimenReferenceId = observation.getSpecimen().getResource().getIdElement().getValue(); + assertThat(specimenReferenceId).startsWith("#"); - // the terser doesn't add a new contained element to the observation - assertThat(observation.getContained()).isEmpty(); + // the terser doesn't add a new contained element to the observation + assertThat(observation.getContained()).isEmpty(); - // However the encoded text contains both a single contained resource, as well as the reference to it. - assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); - assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); - } + } - @Test - public void testReferenceCreatedByIdTypeDoesntMutateContained() { - Specimen specimen = new Specimen(); - specimen.setId("contained-id"); - specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + @Test + public void testReferenceCreatedByIdTypeDoesntMutateContained() { + Specimen specimen = new Specimen(); + specimen.setId("contained-id"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); - Observation observation = new Observation(); - observation.setId("O1"); - observation.setStatus(Observation.ObservationStatus.FINAL); - observation.getContained().add(specimen); - observation.setSpecimen(new Reference(new IdType("#contained-id"))); + Observation observation = new Observation(); + observation.setId("O1"); + observation.setStatus(Observation.ObservationStatus.FINAL); + observation.getContained().add(specimen); + observation.setSpecimen(new Reference(new IdType("#contained-id"))); - String text = ourCtx.newJsonParser().encodeResourceToString(observation); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); - // When encoding, - String specimenReferenceId = observation.getSpecimen().getReference(); - assertThat(specimenReferenceId).startsWith("#"); + // When encoding, + String specimenReferenceId = observation.getSpecimen().getReference(); + assertThat(specimenReferenceId).startsWith("#"); - // FIXME why does the terser mutate the contained element of the original resource? - String specimenContainedId = observation.getContained().get(0).getIdElement().getValue(); - assertThat(specimenContainedId).isEqualTo("contained-id"); + // FIXME why does the terser mutate the contained element of the original resource? + String specimenContainedId = observation.getContained().get(0).getIdElement().getValue(); + assertThat(specimenContainedId).isEqualTo("contained-id"); - // However the encoded text contains both a single contained resource, as well as the reference to it. - assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); - assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + // However the encoded text contains both a single contained resource, as well as the reference to it. + assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + } } + @DatatypeDef( name = "UnknownPrimitiveType" ) From 35beac168b7d5a57a7c9fa41c00a3c65c2d5d860 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 15:59:29 -0500 Subject: [PATCH 05/29] Assert that getResource and contained are same object when read --- .../src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java | 2 ++ 1 file changed, 2 insertions(+) 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 1d0e02adbf34..2222f0e9add2 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 @@ -80,6 +80,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; @@ -1900,6 +1901,7 @@ public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); //FIXME the parsed resource has an added `#` in the contained resource id. + assertSame(observation.getContained().get(0), observation.getSpecimen().getResource()); assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); } From 2447c8c266c7235bbfef76834d962113d3316943 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 16:06:05 -0500 Subject: [PATCH 06/29] Add explicit base case for FHIRPath contained --- .../fhir/r4/utils/FhirPathEngineR4Test.java | 114 ++++++++++++++---- 1 file changed, 93 insertions(+), 21 deletions(-) diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java index 1c2382b6aac1..40dc195cda47 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/utils/FhirPathEngineR4Test.java @@ -2,6 +2,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.fhirpath.BaseValidationTestWithInlineMocks; +import ca.uhn.fhir.model.api.annotation.Description; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.util.TestUtil; import org.hl7.fhir.dstu3.utils.FhirPathEngineTest; @@ -24,6 +25,8 @@ import org.hl7.fhir.r4.model.StructureDefinition; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.List; @@ -31,6 +34,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; public class FhirPathEngineR4Test extends BaseValidationTestWithInlineMocks { @@ -39,32 +43,100 @@ public class FhirPathEngineR4Test extends BaseValidationTestWithInlineMocks { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirPathEngineTest.class); private static FHIRPathEngine ourEngine; - @Test - public void testCrossResourceBoundaries() throws FHIRException { - Specimen specimen = new Specimen(); - specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); - - Observation o = new Observation(); - o.setId("O1"); - o.setStatus(Observation.ObservationStatus.FINAL); - o.setSpecimen(new Reference(specimen)); - IParser p = ourCtx.newJsonParser(); - o = (Observation) p.parseResource(p.encodeResourceToString(o)); - - List value; + @Nested + @DisplayName("FHIRPath access for contained resources.") + class CrossResourceBoundaries{ + + /** + * When a contained resource without an ID is set as a reference directly, the JSON parser will automatically + * generate a valid ID for the contained resource, add it to the and the . + */ + @Test + public void testParserGeneratedContainedId() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + o.setSpecimen(new Reference(specimen)); + + IParser p = ourCtx.newJsonParser(); + o = (Observation) p.parseResource(p.encodeResourceToString(o)); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + assertSpecimenFoundAndDateCorrect(o); + } + + /** If a contained resource with an ID exists in the contained field, it can be referred to by a # prefixed + * reference. + *

+ * This is the most basic FHIR structure that allow FHIRPath to resolve references to contained resources. + **/ + @Test + public void testManuallySetContainedAndReference() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setId("S1"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + o.setSpecimen(new Reference("#S1")); + + o.getContained().add(specimen); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + + assertSpecimenFoundAndDateCorrect(o); + } + + /**If a contained resource with an ID exists in the contained field, it can be referred to by a # prefixed + * reference, as well as included as the resource for that reference + **/ + @Test + public void testManuallySetContainedAndReferenceWithResource() throws FHIRException { + Specimen specimen = new Specimen(); + specimen.setId("S1"); + specimen.setReceivedTimeElement(new DateTimeType("2011-01-01")); + + Observation o = new Observation(); + o.setId("O1"); + o.setStatus(Observation.ObservationStatus.FINAL); + Reference reference = new Reference("#S1"); + reference.setResource(specimen); + o.setSpecimen(reference); + + o.getContained().add(specimen); + + List value; + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); + assertThat(value).hasSize(1); + assertSpecimenFoundAndDateCorrect(o); + } + + private void assertSpecimenFoundAndDateCorrect(Observation o) { + List value; + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve()", Base.class); + assertThat(value).hasSize(1); + + value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve().receivedTime", Base.class); + assertThat(value).hasSize(1); + assertEquals("2011-01-01", ((DateTimeType) value.get(0)).getValueAsString()); + } + } - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen", Base.class); - assertThat(value).hasSize(1); - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve()", Base.class); - assertThat(value).hasSize(1); - value = ourCtx.newFhirPath().evaluate(o, "Observation.specimen.resolve().receivedTime", Base.class); - assertThat(value).hasSize(1); - assertEquals("2011-01-01", ((DateTimeType) value.get(0)).getValueAsString()); - } From 0f990050bf7be65f605e3210d86b31fa22b761a2 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 16:23:46 -0500 Subject: [PATCH 07/29] Test cleanup and some additional assertions --- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 112 ++++++++++-------- 1 file changed, 60 insertions(+), 52 deletions(-) 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 2222f0e9add2..4cace695b0cf 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 @@ -1799,30 +1799,31 @@ public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal */ @Test public void testEncodeContainedBundle() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Bundle\",\n" + - " \"id\": \"REASONS\",\n" + - " \"entry\": [ {\n" + - " \"resource\": {\n" + - " \"resourceType\": \"Condition\",\n" + - " \"id\": \"123\"\n" + - " }\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"MeasureReport\",\n" + - " \"id\": \"MRPT5000602611RD\",\n" + - " \"evaluatedResource\": [ {\n" + - " \"reference\": \"#REASONS\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#MRPT5000602611RD\"\n" + - " }\n" + - " } ]\n" + - "}"; + String auditEvent = """ + { + "resourceType": "AuditEvent", + "contained": [ { + "resourceType": "Bundle", + "id": "REASONS", + "entry": [ { + "resource": { + "resourceType": "Condition", + "id": "123" + } + } ] + }, { + "resourceType": "MeasureReport", + "id": "MRPT5000602611RD", + "evaluatedResource": [ { + "reference": "#REASONS" + } ] + } ], + "entity": [ { + "what": { + "reference": "#MRPT5000602611RD" + } + } ] + }"""; AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae); assertEquals(auditEvent, auditEventAsString); @@ -1834,31 +1835,32 @@ public void testEncodeContainedBundle() { */ @Test public void testParseAndEncodePreservesContainedResourceOrder() { - String auditEvent = "{\n" + - " \"resourceType\": \"AuditEvent\",\n" + - " \"contained\": [ {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"A\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"A\"\n" + - " } ]\n" + - " }, {\n" + - " \"resourceType\": \"Observation\",\n" + - " \"id\": \"B\",\n" + - " \"identifier\": [ {\n" + - " \"value\": \"B\"\n" + - " } ]\n" + - " } ],\n" + - " \"entity\": [ {\n" + - " \"what\": {\n" + - " \"reference\": \"#B\"\n" + - " }\n" + - " }, {\n" + - " \"what\": {\n" + - " \"reference\": \"#A\"\n" + - " }\n" + - " } ]\n" + - "}"; + String auditEvent = """ + { + "resourceType": "AuditEvent", + "contained": [ { + "resourceType": "Observation", + "id": "A", + "identifier": [ { + "value": "A" + } ] + }, { + "resourceType": "Observation", + "id": "B", + "identifier": [ { + "value": "B" + } ] + } ], + "entity": [ { + "what": { + "reference": "#B" + } + }, { + "what": { + "reference": "#A" + } + } ] + }"""; ourLog.info("Input: {}", auditEvent); AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); @@ -1911,9 +1913,10 @@ public void testReferenceCreatedByStringDoesntMutateContained() { observation.setId("123"); Specimen specimen = new Specimen(); specimen.setId("contained-id"); - observation.getContained().add(specimen); observation.setSpecimen(new Reference("#contained-id")); + observation.getContained().add(specimen); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); assertThat(text).contains("\"reference\":\"#contained-id\""); @@ -1934,9 +1937,10 @@ public void testReferenceCreatedByResourceDoesntMutateContained() { Observation observation = new Observation(); observation.setId("123"); - observation.getContained().add(specimen); observation.setSpecimen(new Reference(specimen)); + observation.getContained().add(specimen); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); assertThat(text).contains("\"reference\":\"#contained-id\""); @@ -1968,6 +1972,9 @@ public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { // the terser doesn't add a new contained element to the observation assertThat(observation.getContained()).isEmpty(); + // the terser doesn't mutate the id of the contained resource either + assertThat(specimen.getId()).doesNotStartWith("#"); + // However the encoded text contains both a single contained resource, as well as the reference to it. assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); @@ -1984,9 +1991,10 @@ public void testReferenceCreatedByIdTypeDoesntMutateContained() { Observation observation = new Observation(); observation.setId("O1"); observation.setStatus(Observation.ObservationStatus.FINAL); - observation.getContained().add(specimen); observation.setSpecimen(new Reference(new IdType("#contained-id"))); + observation.getContained().add(specimen); + String text = ourCtx.newJsonParser().encodeResourceToString(observation); // When encoding, From 0cd295c996125fc88f006adda2cc75b0d180c95f Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 17:18:54 -0500 Subject: [PATCH 08/29] Bump core version + fix method signatures --- .../extractor/SearchParamExtractorR4.java | 5 ++++ .../ca/uhn/fhir/jpa/test/BaseJpaR4Test.java | 11 +++++++++ .../validation/ValidatorPolicyAdvisor.java | 11 +++++++++ .../ValidatorResourceFetcherTest.java | 4 +++- .../fhir/r4/hapi/ctx/HapiWorkerContext.java | 24 +++++++++++++++++++ .../fhir/r4/hapi/fluentpath/FhirPathR4.java | 5 ++++ .../validator/FhirDefaultPolicyAdvisor.java | 11 +++++++++ .../validator/ValidatorWrapper.java | 3 ++- pom.xml | 2 +- 9 files changed, 73 insertions(+), 3 deletions(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java index 80ef7ed53de2..6e56e640552c 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorR4.java @@ -208,5 +208,10 @@ public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { return null; } + + @Override + public boolean paramIsType(String name, int index) { + return false; + } } } diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java index eaed24c66dbb..d958c32de2f8 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaR4Test.java @@ -195,6 +195,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Order; @@ -1073,6 +1074,16 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + return null; + } } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java index 10ad382fdd7d..b8264cb6c30d 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/validation/ValidatorPolicyAdvisor.java @@ -31,6 +31,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -127,4 +128,14 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + return null; + } } diff --git a/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java index fb0d9c4ab5c7..0752776b0cff 100644 --- a/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java +++ b/hapi-fhir-storage/src/test/java/ca/uhn/fhir/jpa/validation/ValidatorResourceFetcherTest.java @@ -15,6 +15,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.utils.XVerExtensionManager; +import org.hl7.fhir.r5.utils.validation.ValidatorSession; import org.hl7.fhir.validation.instance.InstanceValidator; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -56,7 +57,8 @@ public void checkFetchByUrl() { InstanceValidator v = new InstanceValidator( wrappedWorkerContext, new FhirInstanceValidator.NullEvaluationContext(), - new XVerExtensionManager(null)); + new XVerExtensionManager(null), + new ValidatorSession()); RequestDetails r = new SystemRequestDetails(); // test Element returnedResource = fetcher.fetch(v, r,"http://www.test-url-for-questionnaire.com/Questionnaire/test-id|1.0.0"); diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java index a0f6d85a5772..c2d7285da3d4 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/HapiWorkerContext.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -488,4 +489,27 @@ public static ConceptValidationOptions convertConceptValidationOptions(Validatio public boolean isPrimitiveType(String theType) { return PRIMITIVE_TYPES.contains(theType); } + + public List fetchResourcesByType(Class theClass) { + List res = new ArrayList<>(); + if (theClass == StructureDefinition.class) { + res.addAll((Collection) getStructures()); + } + return res; + } + + public T fetchResource(Class class_, String uri, Resource source) { + return fetchResource(class_, uri); + } + + @Override + public List fetchTypeDefinitions(String n) { + List types = new ArrayList<>(); + for (StructureDefinition sd : allStructures()) { + if (n.equals(sd.getTypeTail())) { + types.add(sd); + } + } + return types; + } } diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java index 2c04412d4b81..e7fd0f36fe59 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java @@ -158,6 +158,11 @@ public boolean conformsToProfile(FHIRPathEngine engine, Object appContext, Base public ValueSet resolveValueSet(FHIRPathEngine engine, Object appContext, String url) { return null; } + + @Override + public boolean paramIsType(String name, int index) { + return false; + } }); } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java index 83e97cf95724..7ca6ff67e6c0 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/FhirDefaultPolicyAdvisor.java @@ -11,6 +11,7 @@ import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.validation.instance.advisor.BasePolicyAdvisorForFullValidation; import java.util.Arrays; import java.util.EnumSet; @@ -95,4 +96,14 @@ public boolean isSuppressMessageId(String path, String messageId) { public ReferenceValidationPolicy getReferencePolicy() { return ReferenceValidationPolicy.IGNORE; } + + @Override + public IValidationPolicyAdvisor getPolicyAdvisor() { + return new BasePolicyAdvisorForFullValidation(getReferencePolicy()); + } + + @Override + public IValidationPolicyAdvisor setPolicyAdvisor(IValidationPolicyAdvisor policyAdvisor) { + return null; + } } diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java index 4952a7beb3e3..5c5b1b0bb2f1 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/ValidatorWrapper.java @@ -20,6 +20,7 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; +import org.hl7.fhir.r5.utils.validation.ValidatorSession; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.validation.constants.IdStatus; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -120,7 +121,7 @@ public List validate( FHIRPathEngine.IEvaluationContext evaluationCtx = new FhirInstanceValidator.NullEvaluationContext(); XVerExtensionManager xverManager = new XVerExtensionManager(theWorkerContext); try { - v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager); + v = new InstanceValidator(theWorkerContext, evaluationCtx, xverManager, new ValidatorSession()); } catch (Exception e) { throw new ConfigurationException(Msg.code(648) + e.getMessage(), e); } diff --git a/pom.xml b/pom.xml index f6ff522893e2..cc7f72f50181 100644 --- a/pom.xml +++ b/pom.xml @@ -979,7 +979,7 @@ - 6.4.0 + 6.4.4 2.41.1 -Dfile.encoding=UTF-8 -Xmx2048m 3.5.2 From 67b5aae31e86df4b20cb5b48c5ae913f0338b940 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 14 Jan 2025 17:58:38 -0500 Subject: [PATCH 09/29] FIXME to TODO --- .../test/java/ca/uhn/fhir/parser/JsonParserR4Test.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 4cace695b0cf..67e5b9963fd0 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 @@ -1902,7 +1902,7 @@ public void testContainedResourceIdIsReadWithoutAddingHash() throws IOException Observation observation = ourCtx.newJsonParser().parseResource(Observation.class, text); assertThat(observation.getSpecimen().getReference()).isEqualTo("#contained-id"); - //FIXME the parsed resource has an added `#` in the contained resource id. + //TODO the parsed resource has an added `#` in the contained resource id. assertSame(observation.getContained().get(0), observation.getSpecimen().getResource()); assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); } @@ -1923,7 +1923,7 @@ public void testReferenceCreatedByStringDoesntMutateContained() { assertThat(text).contains("\"id\":\"contained-id\""); assertThat(observation.getContained().size()).isEqualTo(1); - /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given + /*TODO despite correctly encoding the contained resource, the original contained resource is mutated and given an id prefixed with a hash. */ assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); @@ -1947,7 +1947,7 @@ public void testReferenceCreatedByResourceDoesntMutateContained() { assertThat(text).contains("\"id\":\"contained-id\""); assertThat(observation.getContained().size()).isEqualTo(1); - /*FIXME despite correctly encoding the contained resource, the original contained resource is mutated and given + /*TODO despite correctly encoding the contained resource, the original contained resource is mutated and given an id prefixed with a hash. */ assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); @@ -2001,7 +2001,7 @@ public void testReferenceCreatedByIdTypeDoesntMutateContained() { String specimenReferenceId = observation.getSpecimen().getReference(); assertThat(specimenReferenceId).startsWith("#"); - // FIXME why does the terser mutate the contained element of the original resource? + // TODO why does the terser mutate the contained element of the original resource? String specimenContainedId = observation.getContained().get(0).getIdElement().getValue(); assertThat(specimenContainedId).isEqualTo("contained-id"); From 2ee317ed5587ff84267635a7d0b5c5c601f70cba Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 15 Jan 2025 19:10:43 -0500 Subject: [PATCH 10/29] Fix wg related validation changes See https://github.com/hapifhir/org.hl7.fhir.core/commit/d7d2ea50f314fde3ccbb333dd8f221e99e841237 --- ...tedResourceValidationSupportFromValidationChainTest.java | 6 +++--- .../hapi/validation/FhirInstanceValidatorDstu3Test.java | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java index 6163dea83f8d..f06c8567c0ea 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaPersistedResourceValidationSupportFromValidationChainTest.java @@ -77,7 +77,7 @@ public void validation_Jpa_Bundle_MeasureReferencesLibraryAndLibrary() { final ValidationResult validationResult = validator.validateWithResult(bundleWithBadLibrary); - assertEquals(12, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(10, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test @@ -94,7 +94,7 @@ public void validation_Jpa_Bundle_MeasureOnly() { final ValidationResult validationResult = validator.validateWithResult(bundleWithMeasureOnly ); - assertEquals(10, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(8, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test @@ -108,7 +108,7 @@ public void validation_Jpa_Bundle_MeasureOnly_NoLibraryReference() { final ValidationResult validationResult = validator.validateWithResult(bundleWithMeasureOnlyNoLibraryReference); - assertEquals(9, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + assertEquals(7, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); } @Test diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java index 775bf7efff4d..16e70d10f95b 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidatorDstu3Test.java @@ -513,9 +513,6 @@ else if (t.getMessage().contains("bdl-7") && t.getMessage().contains("Error eval else if (t.getMessage().contains("side is inherently a collection") && t.getMessage().endsWith("may fail or return false if there is more than one item in the content being evaluated")) { // Some DSTU3 FHIRPath expressions now produce warnings if a singleton is compared to a collection that potentially has > 1 elements return false; - } else if (t.getMessage().contains("When HL7 is publishing a resource, the owning committee must be stated using the http://hl7.org/fhir/StructureDefinition/structuredefinition-wg extension")) { - // DSTU3 resources predate this strict requirement - return false; } else if (t.getMessage().equals("The nominated WG 'rcrim' is unknown")) { //The rcrim workgroup is now brr http://www.hl7.org/Special/committees/rcrim/index.cfm return false; From 349348b0c87d2f01462a36a07545e69f23d105b4 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 15 Jan 2025 19:12:27 -0500 Subject: [PATCH 11/29] Fix format validation message --- .../ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java index ce29bd181cbf..62b9c97255a5 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/JpaResourceDaoSearchParameterTest.java @@ -94,7 +94,7 @@ public void testValidateInvalidExpression() { myDao.validateResourceForStorage(nextSearchParameter, null); fail(); } catch (UnprocessableEntityException e) { - assertEquals(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"Patient.ex[[[\": Error in ?? at 1, 1: Found [ expecting a token name", e.getMessage()); + assertEquals(Msg.code(1121) + "Invalid FHIRPath format for SearchParameter.expression \"Patient.ex[[[\": Error @1, 1: Found [ expecting a token name", e.getMessage()); } } From 7eb1220be470b2995bbb2e7a8e953fca89251ed1 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 15 Jan 2025 19:17:07 -0500 Subject: [PATCH 12/29] Fix format for validation message 2 --- .../ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java index f827a395a3eb..8e32f74dbf37 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java @@ -852,7 +852,7 @@ public void testValidateProfileTargetType_PolicyCheckExistsAndType() throws IOEx obs.setSubject(new Reference("Group/ABC")); oo = validateAndReturnOutcome(obs); ourLog.debug(myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo)); - assertThat(oo.getIssueFirstRep().getDiagnostics()).as(encode(oo)).isEqualTo("Unable to find a match for profile Group/ABC (by type) among choices: ; [CanonicalType[http://hl7.org/fhir/StructureDefinition/Patient]]"); + assertThat(oo.getIssueFirstRep().getDiagnostics()).as(encode(oo)).isEqualTo("Unable to find a match for profile Group/ABC (by type) among choices: ; [http://hl7.org/fhir/StructureDefinition/Patient]"); // Target of right type obs.setSubject(new Reference("Patient/DEF")); From a4f8ed6f10d2dd18cd42632474aaf735df82197e Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 15 Jan 2025 19:19:53 -0500 Subject: [PATCH 13/29] Fix format for validation message 3 --- .../java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java | 2 +- .../java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java b/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java index a2fca4ee0fb2..337e55fa45ec 100644 --- a/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java +++ b/hapi-fhir-jpaserver-hfql/src/test/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutorTest.java @@ -198,7 +198,7 @@ select foo() IHfqlExecutionResult result = myHfqlExecutor.executeInitialSearch(statement, null, mySrd); IHfqlExecutionResult.Row row = result.getNextRow(); assertEquals(IHfqlExecutionResult.ROW_OFFSET_ERROR, row.getRowOffset()); - assertEquals("Failed to evaluate FHIRPath expression \"foo()\". Error: HAPI-2404: Error in ?? at 1, 1: The name foo is not a valid function name", row.getRowValues().get(0)); + assertEquals("Failed to evaluate FHIRPath expression \"foo()\". Error: HAPI-2404: Error @1, 1: The name foo is not a valid function name", row.getRowValues().get(0)); assertFalse(result.hasNext()); } diff --git a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java index a30d7dfc8bb2..a3e4acb2fd19 100644 --- a/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java +++ b/hapi-fhir-server-mdm/src/test/java/ca/uhn/fhir/mdm/rules/config/MdmRuleValidatorTest.java @@ -76,7 +76,7 @@ public void testMatcherBadFhirPath() throws IOException { try { setMdmRuleJson("bad-rules-bad-fhirpath.json"); fail(); } catch (ConfigurationException e) { - assertThat(e.getMessage()).startsWith(Msg.code(1518) + "MatchField [given-name] resourceType [Patient] has failed FHIRPath evaluation. Error in ?? at 1, 1: The name blurst is not a valid function name"); + assertThat(e.getMessage()).startsWith(Msg.code(1518) + "MatchField [given-name] resourceType [Patient] has failed FHIRPath evaluation. Error @1, 1: The name blurst is not a valid function name"); } } From 410ab535323034946b2ada9452feb553514d51ea Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 16 Jan 2025 17:25:25 -0500 Subject: [PATCH 14/29] Try fixing contained by tracking generated contained IDs --- .../java/ca/uhn/fhir/parser/ParserState.java | 11 ++++--- .../java/ca/uhn/fhir/util/FhirTerser.java | 31 ++++++++++--------- .../extractor/BaseSearchParamExtractor.java | 2 +- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 29 ++++++++--------- .../ca/uhn/fhir/util/FhirTerserR4Test.java | 11 ++++--- 5 files changed, 46 insertions(+), 38 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 81a578ecc23a..129b3c30bfc3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java @@ -399,7 +399,7 @@ public void wereBack() { myErrorHandler.containedResourceWithNoId(null); } else { if (!res.getId().isLocal()) { - res.setId(new IdDt('#' + res.getId().getIdPart())); + res.setId(new IdDt(res.getId().getIdPart())); } getPreResourceState().getContainedResources().put(res.getId().getValueAsString(), res); } @@ -439,7 +439,7 @@ public void wereBack() { // need an ID to be referred to) myErrorHandler.containedResourceWithNoId(null); } else { - res.getIdElement().setValue('#' + res.getIdElement().getIdPart()); + res.getIdElement().setValue(res.getIdElement().getIdPart()); getPreResourceState() .getContainedResources() .put(res.getIdElement().getValue(), res); @@ -1238,12 +1238,13 @@ void weaveContainedResources() { String ref = nextRef.getReferenceElement().getValue(); if (isNotBlank(ref)) { if (ref.startsWith("#") && ref.length() > 1) { - IBaseResource target = myContainedResources.get(ref); + String hashLessId = ref.substring(1); + IBaseResource target = myContainedResources.get(hashLessId); if (target != null) { - ourLog.debug("Resource contains local ref {}", ref); + ourLog.debug("Resource contains local ref {}", hashLessId); nextRef.setResource(target); } else { - myErrorHandler.unknownReference(null, ref); + myErrorHandler.unknownReference(null, hashLessId); } } } 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 2a3908fa8993..866a91eabde8 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 @@ -38,6 +38,7 @@ import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.base.composite.BaseContainedDt; import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.parser.DataFormatException; import com.google.common.collect.Lists; @@ -87,6 +88,9 @@ public class FhirTerser { private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED = FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED"; + private static final String USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER = + FhirTerser.class.getName() + "_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER"; + private final FhirContext myContext; /** @@ -1456,7 +1460,7 @@ private void containResourcesForEncoding( for (IBaseReference next : allReferences) { IBaseResource resource = next.getResource(); if (resource != null) { - if (resource.getIdElement().isEmpty() || resource.getIdElement().isLocal()) { + if (resource.getIdElement().isEmpty() || theContained.hasTerserGeneratedContainedId(resource)) { IIdType id = theContained.addContained(resource); if (id == null) { @@ -1466,7 +1470,7 @@ private void containResourcesForEncoding( getContainedResourceList(theResource).add(resource); next.setReference(id.getValue()); } - if (resource.getIdElement().isLocal() && theContained.hasExistingIdToContainedResource()) { + if (theContained.hasTerserGeneratedContainedId(resource) && theContained.hasExistingIdToContainedResource()) { theContained .getExistingIdToContainedResource() .remove(resource.getIdElement().getValue()); @@ -1507,16 +1511,7 @@ public ContainedResources containResources(IBaseResource theResource, OptionsEnu ContainedResources contained = new ContainedResources(); List containedResources = getContainedResourceList(theResource); - for (IBaseResource next : containedResources) { - String nextId = next.getIdElement().getValue(); - if (StringUtils.isNotBlank(nextId)) { - if (!nextId.startsWith("#")) { - nextId = '#' + nextId; - } - next.getIdElement().setValue(nextId); - } - contained.addContained(next); - } + containedResources.forEach(contained::addContained); if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { containResourcesForEncoding(contained, theResource, modifyResource); @@ -1810,9 +1805,12 @@ public IIdType addContained(IBaseResource theResource) { return existing; } - IIdType newId = theResource.getIdElement(); + IIdType newId = new IdDt(theResource.getIdElement()); if (isBlank(newId.getValue())) { - newId.setValue("#" + UUID.randomUUID()); + UUID randomUUID = UUID.randomUUID(); + theResource.getIdElement().setValue(randomUUID.toString()); + theResource.setUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER, Boolean.TRUE); + newId.setValue("#" + randomUUID); } getResourceToIdMap().put(theResource, newId); @@ -1820,6 +1818,11 @@ public IIdType addContained(IBaseResource theResource) { return newId; } + public boolean hasTerserGeneratedContainedId(IBaseResource theResource) { + Object userData = theResource.getUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER); + return userData != null ? (Boolean) userData : false; + } + public void addContained(IIdType theId, IBaseResource theResource) { if (!getResourceToIdMap().containsKey(theResource)) { getResourceToIdMap().put(theResource, theId); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 6c23590d2c8b..59cc122f0f40 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -1676,7 +1676,7 @@ private void cleanUpContainedResourceReferences( .newTerser() .containResources( theResource, - FhirTerser.OptionsEnum.MODIFY_RESOURCE, + FhirTerser.OptionsEnum. MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); } } 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 67e5b9963fd0..72f8c18406c2 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 @@ -1625,10 +1625,10 @@ public void testDuplicateContainedResourcesAcrossABundleAreReplicated() { String encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(b); //Then: Diag should contain one local contained specimen - assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"}]"); + assertThat(encoded).contains("[{\"resource\":{\"resourceType\":\"DiagnosticReport\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId() +"\"}]"); //Then: Obs should contain one local contained specimen, and one local contained pract - assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId().replaceFirst("#", "") +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId().replaceAll("#","") + "\"}]"); - assertThat(encoded).contains("\"performer\":[{\"reference\":\""+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\""+specimen.getId()+"\"}"); + assertThat(encoded).contains("\"resource\":{\"resourceType\":\"Observation\",\"contained\":[{\"resourceType\":\"Specimen\",\"id\":\""+ specimen.getId() +"\"},{\"resourceType\":\"Practitioner\",\"id\":\"" + practitioner.getId() + "\"}]"); + assertThat(encoded).contains("\"performer\":[{\"reference\":\"#"+practitioner.getId()+"\"}],\"specimen\":{\"reference\":\"#"+specimen.getId()+"\"}"); //Also, reverting the operation should work too! Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, encoded); @@ -1654,9 +1654,9 @@ public void testContainedResourcesNotAutoContainedWhenConfiguredNotToDoSo() { ourCtx.getParserOptions().setAutoContainReferenceTargetsWithNoId(true); encoded = ourCtx.newJsonParser().setPrettyPrint(false).encodeResourceToString(md); - String guidWithHash = med.getId(); - String withoutHash = guidWithHash.replace("#", ""); - assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + withoutHash + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + guidWithHash +"\"}}"); //Note we dont check exact ID since its a GUID + String containedResourceId = med.getId(); + String containedResourceReferenceId = "#" + containedResourceId; + assertThat(encoded).contains("{\"resourceType\":\"MedicationDispense\",\"contained\":[{\"resourceType\":\"Medication\",\"id\":\"" + containedResourceId + "\",\"code\":{\"text\":\"MED\"}}],\"identifier\":[{\"value\":\"DISPENSE\"}],\"medicationReference\":{\"reference\":\"" + containedResourceReferenceId +"\"}}"); //Note we dont check exact ID since its a GUID } @Test @@ -1742,7 +1742,7 @@ public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal Observation obs = new Observation(); Patient pt = new Patient(); - pt.setId("#1"); + pt.setId("1"); pt.addName().setFamily("FAM"); obs.getSubject().setReference("#1"); obs.getContained().add(pt); @@ -1755,7 +1755,7 @@ public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal ourLog.info(encoded); obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); + assertEquals("1", obs.getContained().get(0).getId()); assertEquals(enc.getId(), obs.getContained().get(1).getId()); pt = (Patient) obs.getSubject().getResource(); @@ -1784,7 +1784,7 @@ public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal ourLog.info(encoded); obs = ourCtx.newJsonParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); + assertEquals("1", obs.getContained().get(0).getId()); assertEquals(pt.getId(), obs.getContained().get(1).getId()); pt = (Patient) obs.getSubject().getResource(); @@ -1864,8 +1864,8 @@ public void testParseAndEncodePreservesContainedResourceOrder() { ourLog.info("Input: {}", auditEvent); AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent); - assertEquals("#A", ae.getContained().get(0).getId()); - assertEquals("#B", ae.getContained().get(1).getId()); + assertEquals("A", ae.getContained().get(0).getId()); + assertEquals("B", ae.getContained().get(1).getId()); assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); @@ -1967,7 +1967,8 @@ public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { // When encoding, if the reference does not have an id, it is generated in the FhirTerser String specimenReferenceId = observation.getSpecimen().getResource().getIdElement().getValue(); - assertThat(specimenReferenceId).startsWith("#"); + assertThat(specimenReferenceId).doesNotStartWith("#"); + assertSame(observation.getSpecimen().getResource(), specimen); // the terser doesn't add a new contained element to the observation assertThat(observation.getContained()).isEmpty(); @@ -1976,8 +1977,8 @@ public void testReferenceCreatedByResourceDoesntMutateEmptyContained() { assertThat(specimen.getId()).doesNotStartWith("#"); // However the encoded text contains both a single contained resource, as well as the reference to it. - assertThat(text).contains("\"reference\":\""+specimenReferenceId+"\""); - assertThat(text).contains("\"id\":\""+specimenReferenceId.substring(1)+"\""); + assertThat(text).contains("\"reference\":\"#"+specimenReferenceId+"\""); + assertThat(text).contains("\"id\":\""+specimenReferenceId+"\""); } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java index 0f2e122fd6f9..991285d3c915 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java @@ -18,6 +18,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.BooleanType; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DiagnosticReport; import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.Enumeration; import org.hl7.fhir.r4.model.Enumerations; @@ -42,6 +43,7 @@ import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ResourceType; import org.hl7.fhir.r4.model.SimpleQuantity; +import org.hl7.fhir.r4.model.Specimen; import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Substance; import org.hl7.fhir.r4.model.ValueSet; @@ -68,6 +70,7 @@ import java.util.stream.Collectors; import static ca.uhn.fhir.test.utilities.UuidUtils.HASH_UUID_PATTERN; +import static ca.uhn.fhir.test.utilities.UuidUtils.UUID_PATTERN; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -218,12 +221,12 @@ public void testContainedResourcesWithModify_DoubleLink() { myCtx.newTerser().containResources(medAdmin, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); - assertThat(medAdmin.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN); - assertThat(medAdmin.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN); + assertThat(medAdmin.getContained().get(0).getId()).containsPattern(UUID_PATTERN); + assertThat(medAdmin.getContained().get(1).getId()).containsPattern(UUID_PATTERN); assertEquals(ResourceType.Medication, medAdmin.getContained().get(0).getResourceType()); assertEquals(ResourceType.Substance, medAdmin.getContained().get(1).getResourceType()); - assertEquals(medAdmin.getContained().get(0).getId(), medAdmin.getMedicationReference().getReference()); - assertEquals(medAdmin.getContained().get(1).getId(), ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference()); + assertEquals("#" + medAdmin.getContained().get(0).getId(), medAdmin.getMedicationReference().getReference()); + assertEquals("#" + medAdmin.getContained().get(1).getId(), ((Medication) (medAdmin.getContained().get(0))).getIngredientFirstRep().getItemReference().getReference()); } From e3fabda66867014c90e3fbba743d4b365db68eca Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 20 Jan 2025 16:40:40 -0500 Subject: [PATCH 15/29] Use different logic to identify internal fragments --- .../java/ca/uhn/fhir/util/FhirTerser.java | 41 ++++++++++++------- .../extractor/BaseSearchParamExtractor.java | 2 +- 2 files changed, 28 insertions(+), 15 deletions(-) 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 866a91eabde8..07199738b5a7 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 @@ -88,9 +88,6 @@ public class FhirTerser { private static final String USER_DATA_KEY_CONTAIN_RESOURCES_COMPLETED = FhirTerser.class.getName() + "_CONTAIN_RESOURCES_COMPLETED"; - private static final String USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER = - FhirTerser.class.getName() + "_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER"; - private final FhirContext myContext; /** @@ -1432,6 +1429,25 @@ public boolean acceptUndeclaredExtension( }); } + private boolean isInternalFragment(IBaseReference theReference) { + if (!theReference.getReferenceElement().isEmpty()) { + return theReference.getReferenceElement().isLocal(); + } + if( theReference.getResource() == null) { + return false; + } + if (theReference.getResource().getIdElement() == null) { + return false; + } + if (theReference.getResource().getIdElement().isAbsolute()) { + return false; + } + if (theReference.getResource().getIdElement().hasResourceType()) { + return false; + } + return true; + } + private void containResourcesForEncoding( ContainedResources theContained, IBaseResource theResource, boolean theModifyResource) { List allReferences = getAllPopulatedChildElementsOfType(theResource, IBaseReference.class); @@ -1444,7 +1460,7 @@ private void containResourcesForEncoding( for (IBaseReference next : allReferences) { IBaseResource resource = next.getResource(); - if (resource == null && next.getReferenceElement().isLocal()) { + if (resource == null && isInternalFragment(next)) { if (theContained.hasExistingIdToContainedResource()) { IBaseResource potentialTarget = theContained .getExistingIdToContainedResource() @@ -1460,7 +1476,7 @@ private void containResourcesForEncoding( for (IBaseReference next : allReferences) { IBaseResource resource = next.getResource(); if (resource != null) { - if (resource.getIdElement().isEmpty() || theContained.hasTerserGeneratedContainedId(resource)) { + if (resource.getIdElement().isEmpty() || isInternalFragment(next)) { IIdType id = theContained.addContained(resource); if (id == null) { @@ -1470,7 +1486,8 @@ private void containResourcesForEncoding( getContainedResourceList(theResource).add(resource); next.setReference(id.getValue()); } - if (theContained.hasTerserGeneratedContainedId(resource) && theContained.hasExistingIdToContainedResource()) { + if (isInternalFragment(next) + && theContained.hasExistingIdToContainedResource()) { theContained .getExistingIdToContainedResource() .remove(resource.getIdElement().getValue()); @@ -1511,7 +1528,9 @@ public ContainedResources containResources(IBaseResource theResource, OptionsEnu ContainedResources contained = new ContainedResources(); List containedResources = getContainedResourceList(theResource); - containedResources.forEach(contained::addContained); + for (IBaseResource next : containedResources) { + contained.addContained(next); + } if (myContext.getParserOptions().isAutoContainReferenceTargetsWithNoId()) { containResourcesForEncoding(contained, theResource, modifyResource); @@ -1809,20 +1828,14 @@ public IIdType addContained(IBaseResource theResource) { if (isBlank(newId.getValue())) { UUID randomUUID = UUID.randomUUID(); theResource.getIdElement().setValue(randomUUID.toString()); - theResource.setUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER, Boolean.TRUE); + //theResource.setUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER, Boolean.TRUE); newId.setValue("#" + randomUUID); } - getResourceToIdMap().put(theResource, newId); getOrCreateResourceList().add(theResource); return newId; } - public boolean hasTerserGeneratedContainedId(IBaseResource theResource) { - Object userData = theResource.getUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER); - return userData != null ? (Boolean) userData : false; - } - public void addContained(IIdType theId, IBaseResource theResource) { if (!getResourceToIdMap().containsKey(theResource)) { getResourceToIdMap().put(theResource, theId); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 59cc122f0f40..6c23590d2c8b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -1676,7 +1676,7 @@ private void cleanUpContainedResourceReferences( .newTerser() .containResources( theResource, - FhirTerser.OptionsEnum. MODIFY_RESOURCE, + FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); } } From 198854f1538260365b29134e2cc4069a7d8a62b7 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 20 Jan 2025 16:47:34 -0500 Subject: [PATCH 16/29] Remove dead code + spotless --- .../src/main/java/ca/uhn/fhir/util/FhirTerser.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) 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 07199738b5a7..d7e11574dc00 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 @@ -1433,7 +1433,7 @@ private boolean isInternalFragment(IBaseReference theReference) { if (!theReference.getReferenceElement().isEmpty()) { return theReference.getReferenceElement().isLocal(); } - if( theReference.getResource() == null) { + if (theReference.getResource() == null) { return false; } if (theReference.getResource().getIdElement() == null) { @@ -1486,8 +1486,7 @@ private void containResourcesForEncoding( getContainedResourceList(theResource).add(resource); next.setReference(id.getValue()); } - if (isInternalFragment(next) - && theContained.hasExistingIdToContainedResource()) { + if (isInternalFragment(next) && theContained.hasExistingIdToContainedResource()) { theContained .getExistingIdToContainedResource() .remove(resource.getIdElement().getValue()); @@ -1828,7 +1827,6 @@ public IIdType addContained(IBaseResource theResource) { if (isBlank(newId.getValue())) { UUID randomUUID = UUID.randomUUID(); theResource.getIdElement().setValue(randomUUID.toString()); - //theResource.setUserData(USER_DATA_KEY_CONTAINED_RESOURCE_ID_GENERATED_BY_TERSER, Boolean.TRUE); newId.setValue("#" + randomUUID); } getResourceToIdMap().put(theResource, newId); From 4acbb2a0fe79db9ed7c75c2ddffa151abbb5a8bb Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 21 Jan 2025 11:37:01 -0500 Subject: [PATCH 17/29] Naive solution for search param extraction --- .../extractor/SearchParamExtractorService.java | 2 +- .../java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java | 4 ++-- .../java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java | 4 ++-- .../src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java | 4 ++-- .../src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 1d177e53d5a1..489f14a29a47 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -462,7 +462,7 @@ private void extractSearchIndexParametersForTargetResources( private IBaseResource findContainedResource(Collection resources, IBaseReference reference) { for (IBaseResource resource : resources) { - if (resource.getIdElement().equals(reference.getReferenceElement())) return resource; + if (("#" + resource.getIdElement()).equals(reference.getReferenceElement().toString())) return resource; } return null; } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java index c97ff21fc753..ea7cd1e05b7c 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java @@ -1025,7 +1025,7 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource Patient p = new Patient(); p.setId("pat"); p.addName().setFamily("Smith").addGiven("John"); - p.getManagingOrganization().setReference(org.getId()); + p.getManagingOrganization().setReference("#" + org.getId()); Observation obs = new Observation(); obs.getContained().add(p); @@ -1036,7 +1036,7 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource Device d = new Device(); d.setId("dev"); - d.getOwner().setReference(org.getId()); + d.getOwner().setReference("#" + org.getId()); Observation obs2 = new Observation(); obs2.getContained().add(d); diff --git a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java index 4c6fea901d86..53fbfe8b1229 100644 --- a/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java +++ b/hapi-fhir-structures-dstu2.1/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2_1Test.java @@ -403,7 +403,7 @@ public void testEncodeAndParseContained() { assertNotNull(patient.getManagingOrganization().getResource()); org = (Organization) patient.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getIdElement().getValue()); + assertEquals( organizationUuid, org.getIdElement().getValue()); assertEquals("Contained Test Organization", org.getName()); // And re-encode a second time @@ -817,7 +817,7 @@ public void testEncodeBinaryWithNoContentType() { public void testEncodeBundleWithContained() { DiagnosticReport rpt = new DiagnosticReport(); rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Sharp1")).setId("#1")); - rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Uuid1")).setId("urn:uuid:UUID1")); + rpt.addResult().setResource(new Observation().setCode(new CodeableConcept().setText("Uuid1")).setId("https//example.org/UUID1")); Bundle b = new Bundle(); b.addEntry().setResource(rpt); 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 a310005e56d1..a5416106b4ea 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 @@ -114,8 +114,8 @@ public void testParseAndEncodePreservesContainedResourceOrder() { ourLog.info("Input: {}", auditEvent); AuditEvent ae = ourCtx.newXmlParser().parseResource(AuditEvent.class, auditEvent); - assertEquals("#A", ae.getContained().get(0).getId()); - assertEquals("#B", ae.getContained().get(1).getId()); + assertEquals("A", ae.getContained().get(0).getId()); + assertEquals("B", ae.getContained().get(1).getId()); assertEquals("#B", ae.getEntity().get(0).getWhat().getReference()); assertEquals("#A", ae.getEntity().get(1).getWhat().getReference()); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java index 991285d3c915..bf0973bdc932 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/util/FhirTerserR4Test.java @@ -197,12 +197,12 @@ public void testContainResourcesWithModify() { FhirTerser.ContainedResources contained = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); - assertThat(mr.getContained().get(0).getId()).containsPattern(HASH_UUID_PATTERN); - assertThat(mr.getContained().get(1).getId()).containsPattern(HASH_UUID_PATTERN); + assertThat(mr.getContained().get(0).getId()).containsPattern(UUID_PATTERN); + assertThat(mr.getContained().get(1).getId()).containsPattern(UUID_PATTERN); assertEquals(ResourceType.Medication, mr.getContained().get(0).getResourceType()); assertEquals(ResourceType.Practitioner, mr.getContained().get(1).getResourceType()); - assertEquals(mr.getContained().get(0).getId(), mr.getMedicationReference().getReference()); - assertEquals(mr.getContained().get(1).getId(), mr.getRequester().getReference()); + assertEquals("#" + mr.getContained().get(0).getId(), mr.getMedicationReference().getReference()); + assertEquals("#" + mr.getContained().get(1).getId(), mr.getRequester().getReference()); FhirTerser.ContainedResources secondPass = myCtx.newTerser().containResources(mr, FhirTerser.OptionsEnum.MODIFY_RESOURCE, FhirTerser.OptionsEnum.STORE_AND_REUSE_RESULTS); assertThat(secondPass).isSameAs(contained); From 1a76c776e0df8e02d1f15d4344d406ed2b951414 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 21 Jan 2025 18:06:48 -0500 Subject: [PATCH 18/29] Better isInternalFragment, still naive --- .../java/ca/uhn/fhir/util/FhirTerser.java | 20 +++++++------------ .../dao/r4/FhirResourceDaoR4CreateTest.java | 4 ++-- .../ca/uhn/fhir/parser/JsonParserR4Test.java | 4 ++-- 3 files changed, 11 insertions(+), 17 deletions(-) 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 d7e11574dc00..3fec9ed9fa12 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 @@ -1430,19 +1430,13 @@ public boolean acceptUndeclaredExtension( } private boolean isInternalFragment(IBaseReference theReference) { - if (!theReference.getReferenceElement().isEmpty()) { - return theReference.getReferenceElement().isLocal(); + assert theReference.getResource() != null; + if (theReference.getResource().getIdElement().isEmpty()) { + return true; } - if (theReference.getResource() == null) { - return false; - } - if (theReference.getResource().getIdElement() == null) { - return false; - } - if (theReference.getResource().getIdElement().isAbsolute()) { - return false; - } - if (theReference.getResource().getIdElement().hasResourceType()) { + if (theReference.getResource().getIdElement().isAbsolute() + || theReference.getResource().getIdElement().getValueAsString().startsWith("urn:") + ) { return false; } return true; @@ -1460,7 +1454,7 @@ private void containResourcesForEncoding( for (IBaseReference next : allReferences) { IBaseResource resource = next.getResource(); - if (resource == null && isInternalFragment(next)) { + if (resource == null && next.getReferenceElement().isLocal()) { if (theContained.hasExistingIdToContainedResource()) { IBaseResource potentialTarget = theContained .getExistingIdToContainedResource() diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index 67105890219f..f2a16aca1dae 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -168,7 +168,7 @@ public void testCreateLinkCreatesAppropriatePaths_ContainedResource() { myPatientDao.update(p, mySrd); Observation containedObs = new Observation(); - containedObs.setId("#cont"); + containedObs.setId("cont"); containedObs.setSubject(new Reference("Patient/A")); Encounter enc = new Encounter(); @@ -285,7 +285,7 @@ public void testCreateLinkCreatesAppropriatePaths_ContainedResourceRecursive_ToO p.setManagingOrganization(new Reference("Organization/ABC")); Observation containedObs = new Observation(); - containedObs.setId("#cont"); + containedObs.setId("cont"); containedObs.setSubject(new Reference("#pat")); Encounter enc = new Encounter(); 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 72f8c18406c2..5bfeaa60e84d 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 @@ -1685,11 +1685,11 @@ public void testDuplicateContainedResourcesNotOutputtedTwiceWithManualIds() { MedicationDispense md = new MedicationDispense(); MedicationRequest mr = new MedicationRequest(); - mr.setId("#MR"); + mr.setId("MR"); md.addAuthorizingPrescription().setResource(mr); Medication med = new Medication(); - med.setId("#MED"); + med.setId("MED"); md.setMedication(new Reference(med)); mr.setMedication(new Reference(med)); From c089a447778023926614118583b5abd97ab23b10 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 22 Jan 2025 16:28:22 -0500 Subject: [PATCH 19/29] Add hash to reference when modifying for search params --- hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3fec9ed9fa12..2c86913fd70e 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 @@ -1478,7 +1478,7 @@ private void containResourcesForEncoding( } if (theModifyResource) { getContainedResourceList(theResource).add(resource); - next.setReference(id.getValue()); + next.setReference("#" + id.getValue()); } if (isInternalFragment(next) && theContained.hasExistingIdToContainedResource()) { theContained From 659980cb82275a50eef848ca4cfe21773c2c0fef Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 22 Jan 2025 17:36:15 -0500 Subject: [PATCH 20/29] Fix double # when adding ref id for modifyResource=true --- hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTerser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2c86913fd70e..7417aae9e35f 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 @@ -1817,7 +1817,7 @@ public IIdType addContained(IBaseResource theResource) { return existing; } - IIdType newId = new IdDt(theResource.getIdElement()); + final IIdType newId = new IdDt(theResource.getIdElement()); if (isBlank(newId.getValue())) { UUID randomUUID = UUID.randomUUID(); theResource.getIdElement().setValue(randomUUID.toString()); @@ -1825,7 +1825,7 @@ public IIdType addContained(IBaseResource theResource) { } getResourceToIdMap().put(theResource, newId); getOrCreateResourceList().add(theResource); - return newId; + return theResource.getIdElement(); } public void addContained(IIdType theId, IBaseResource theResource) { From 774016437b9852047ab516989731bbfd723a3ffb Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 24 Jan 2025 15:41:56 -0500 Subject: [PATCH 21/29] Fix hashless ref ID in test --- .../java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java index ea7cd1e05b7c..b4aa30c63cdf 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java @@ -715,8 +715,8 @@ public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheC obs.getCode().addCoding().setCode("obs2").setSystem("Some System").setDisplay("Body weight as measured by me"); obs.setStatus(Observation.ObservationStatus.FINAL); obs.setValue(new Quantity(81)); - obs.setSubject(new Reference(p.getId())); - obs.setEncounter(new Reference(encounter.getId())); + obs.setSubject(new Reference("#" +p.getId())); + obs.setEncounter(new Reference("#" + encounter.getId())); oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); // Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record From 4e7a5e5cd3e8e1f20d77ad11af5fb3abb0101128 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 24 Jan 2025 15:42:36 -0500 Subject: [PATCH 22/29] Consistent hash use in getResourceToIdMap() --- .../src/main/java/ca/uhn/fhir/util/FhirTerser.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 7417aae9e35f..7ead02f327cb 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 @@ -1822,8 +1822,13 @@ public IIdType addContained(IBaseResource theResource) { UUID randomUUID = UUID.randomUUID(); theResource.getIdElement().setValue(randomUUID.toString()); newId.setValue("#" + randomUUID); + //TODO put newId in resourceToIdMap ? + getResourceToIdMap().put(theResource, newId); + } else { + //TODO put new IdDt with # in resourceToIdMap ? + getResourceToIdMap().put(theResource, new IdDt("#" + newId.getIdPart())); } - getResourceToIdMap().put(theResource, newId); + //getResourceToIdMap().put(theResource, newId); getOrCreateResourceList().add(theResource); return theResource.getIdElement(); } From fdcbea27b0cb2b13b6de121ac64e5743ec4fce1b Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 24 Jan 2025 17:22:33 -0500 Subject: [PATCH 23/29] Fix extra # in test --- .../src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5bfeaa60e84d..4a602ca52761 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 @@ -1775,7 +1775,7 @@ public void testEncodeResourceWithMixedManualAndAutomaticContainedResourcesLocal obs.getSubject().setResource(pt); Encounter enc = new Encounter(); - enc.setId("#1"); + enc.setId("1"); enc.setStatus(Encounter.EncounterStatus.ARRIVED); obs.getEncounter().setReference("#1"); obs.getContained().add(enc); From 70297b260dfcab6cc0eed23361d60eb73da0a880 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 24 Jan 2025 17:57:34 -0500 Subject: [PATCH 24/29] Fix unneeded # in test --- .../ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java index f2a16aca1dae..7badf1e99b68 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4CreateTest.java @@ -754,7 +754,7 @@ public void testTagsInContainedResourcesPreserved() { assertEquals(organizationUuid, organizationUuidParsed); Organization org = (Organization) p.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getId()); + assertEquals(organizationUuid, org.getId()); assertThat(org.getMeta().getTag()).hasSize(1); } From d5c15372ae45e4081da1d87a5b2eba6271a84045 Mon Sep 17 00:00:00 2001 From: dotasek Date: Fri, 24 Jan 2025 17:59:25 -0500 Subject: [PATCH 25/29] Add # when replacing an empty ref with a resource id --- .../jpa/searchparam/extractor/BaseSearchParamExtractor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index 6c23590d2c8b..6636d60158ca 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -50,6 +50,7 @@ import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.api.TemporalPrecisionEnum; import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.param.DateParam; @@ -2237,7 +2238,7 @@ private void extractResourceLinkFromReference( IBaseReference valueRef) { IIdType nextId = valueRef.getReferenceElement(); if (nextId.isEmpty() && valueRef.getResource() != null) { - nextId = valueRef.getResource().getIdElement(); + nextId = new IdDt("#" + valueRef.getResource().getIdElement()); } if (nextId == null || nextId.isEmpty()) { From cf7e4e806e5dd75a738a800b0d70f7f7a0084f48 Mon Sep 17 00:00:00 2001 From: dotasek Date: Tue, 28 Jan 2025 10:06:27 -0500 Subject: [PATCH 26/29] Fix more # based test failures --- .../ca/uhn/fhir/parser/XmlParserDstu3Test.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java index 2388fa72413d..895a8bfeb4b4 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/XmlParserDstu3Test.java @@ -585,7 +585,7 @@ public void testEncodeAndParseContained() { assertNotNull(patient.getManagingOrganization().getResource()); org = (Organization) patient.getManagingOrganization().getResource(); - assertEquals("#" + organizationUuid, org.getIdElement().getValue()); + assertEquals(organizationUuid, org.getIdElement().getValue()); assertEquals("Contained Test Organization", org.getName()); // And re-encode a second time @@ -607,7 +607,7 @@ public void testEncodeAndParseContained() { // And re-encode once more, with the references cleared and a manually set local ID patient.getContained().clear(); patient.getManagingOrganization().setReference(null); - patient.getManagingOrganization().getResource().setId(("#333")); + patient.getManagingOrganization().getResource().setId(("333")); encoded = xmlParser.encodeResourceToString(patient); ourLog.info(encoded); assertThat(encoded).contains(Arrays.asList("", "", "", "")); @@ -1324,7 +1324,7 @@ public void testEncodeContainedResources() { // Adding medication to Contained. Medication medResource = new Medication(); medResource.setCode(codeDt); - medResource.setId("#" + medId); + medResource.setId(medId); medicationPrescript.getContained().add(medResource); // Medication reference. This should point to the contained resource. @@ -2055,17 +2055,17 @@ public void testEncodeWithContained() { // Will be added by reference Patient p = new Patient(); - p.setId("#" + "1000"); + p.setId("1000"); contained.add(p); // Will be added by direct resource object Location l = new Location(); - l.setId("#" + "1001"); + l.setId("1001"); contained.add(l); // Will not be referred to (and therefore shouldn't appear in output) Location l2 = new Location(); - l2.setId("#1002"); + l2.setId("1002"); contained.add(l2); Appointment appointment = new Appointment(); @@ -3410,10 +3410,10 @@ public void testParseWovenContainedResources() throws IOException { DiagnosticReport resource = (DiagnosticReport) bundle.getEntry().get(0).getResource(); Observation obs = (Observation) resource.getResult().get(1).getResource(); - assertEquals("#2", obs.getId()); + assertEquals("2", obs.getId()); Reference performerFirstRep = obs.getPerformerFirstRep(); Practitioner performer = (Practitioner) performerFirstRep.getResource(); - assertEquals("#3", performer.getId()); + assertEquals("3", performer.getId()); } /** From 7d00b1cf4439b6565f5e4a6773679835c2b93525 Mon Sep 17 00:00:00 2001 From: "dotasek.dev" Date: Tue, 11 Feb 2025 12:05:01 -0500 Subject: [PATCH 27/29] Apply spotless --- .../src/main/java/ca/uhn/fhir/util/FhirTerser.java | 9 ++++----- .../extractor/SearchParamExtractorService.java | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) 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 7ead02f327cb..5917e6e8bd59 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 @@ -1435,8 +1435,7 @@ private boolean isInternalFragment(IBaseReference theReference) { return true; } if (theReference.getResource().getIdElement().isAbsolute() - || theReference.getResource().getIdElement().getValueAsString().startsWith("urn:") - ) { + || theReference.getResource().getIdElement().getValueAsString().startsWith("urn:")) { return false; } return true; @@ -1822,13 +1821,13 @@ public IIdType addContained(IBaseResource theResource) { UUID randomUUID = UUID.randomUUID(); theResource.getIdElement().setValue(randomUUID.toString()); newId.setValue("#" + randomUUID); - //TODO put newId in resourceToIdMap ? + // TODO put newId in resourceToIdMap ? getResourceToIdMap().put(theResource, newId); } else { - //TODO put new IdDt with # in resourceToIdMap ? + // TODO put new IdDt with # in resourceToIdMap ? getResourceToIdMap().put(theResource, new IdDt("#" + newId.getIdPart())); } - //getResourceToIdMap().put(theResource, newId); + // getResourceToIdMap().put(theResource, newId); getOrCreateResourceList().add(theResource); return theResource.getIdElement(); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index 489f14a29a47..6f67fdfd9e85 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -462,7 +462,8 @@ private void extractSearchIndexParametersForTargetResources( private IBaseResource findContainedResource(Collection resources, IBaseReference reference) { for (IBaseResource resource : resources) { - if (("#" + resource.getIdElement()).equals(reference.getReferenceElement().toString())) return resource; + if (("#" + resource.getIdElement()) + .equals(reference.getReferenceElement().toString())) return resource; } return null; } From cf494b55429eecfdceeba4fa4c5fc53d003fbf8c Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 24 Feb 2025 09:34:27 -0500 Subject: [PATCH 28/29] Revert over-eager hash additions to existing resources on server --- .../java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java | 8 +++++--- .../test/java/ca/uhn/fhir/parser/JsonParserR4Test.java | 4 +--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java index b4aa30c63cdf..4cbee81e6711 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/ChainingR4SearchTest.java @@ -716,7 +716,7 @@ public void testShouldResolveAThreeLinkChainWithAContainedResourceAtTheEndOfTheC obs.setStatus(Observation.ObservationStatus.FINAL); obs.setValue(new Quantity(81)); obs.setSubject(new Reference("#" +p.getId())); - obs.setEncounter(new Reference("#" + encounter.getId())); + obs.setEncounter(new Reference(encounter.getId())); oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); // Create a dummy record so that an unconstrained query doesn't pass the test due to returning the only record @@ -1024,8 +1024,9 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource Patient p = new Patient(); p.setId("pat"); + //p.getContained().add(org); p.addName().setFamily("Smith").addGiven("John"); - p.getManagingOrganization().setReference("#" + org.getId()); + p.getManagingOrganization().setReference(org.getId()); Observation obs = new Observation(); obs.getContained().add(p); @@ -1035,8 +1036,9 @@ public void testShouldResolveAThreeLinkChainWithQualifiersWithAContainedResource oid1 = myObservationDao.create(obs, mySrd).getId().toUnqualifiedVersionless(); Device d = new Device(); + //d.getContained().add(org); d.setId("dev"); - d.getOwner().setReference("#" + org.getId()); + d.getOwner().setReference(org.getId()); Observation obs2 = new Observation(); obs2.getContained().add(d); 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 4a602ca52761..8fee9831cb7a 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 @@ -1947,9 +1947,7 @@ public void testReferenceCreatedByResourceDoesntMutateContained() { assertThat(text).contains("\"id\":\"contained-id\""); assertThat(observation.getContained().size()).isEqualTo(1); - /*TODO despite correctly encoding the contained resource, the original contained resource is mutated and given - an id prefixed with a hash. - */ + assertThat(observation.getContained().get(0).getId()).isEqualTo("contained-id"); } From 1c6b9f34ea248e38c861d58ef0585312316302c7 Mon Sep 17 00:00:00 2001 From: dotasek Date: Mon, 24 Feb 2025 11:20:02 -0500 Subject: [PATCH 29/29] WIP hacky fix to check for ResourceType/ID references --- .../src/main/java/ca/uhn/fhir/util/FhirTerser.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 5917e6e8bd59..68a0ea387008 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 @@ -1434,10 +1434,17 @@ private boolean isInternalFragment(IBaseReference theReference) { if (theReference.getResource().getIdElement().isEmpty()) { return true; } + // Check if the reference is an external reference + // TODO Fix Hack: ensure that this conforms to what FHIR considers (URLs, URNs, and more) if (theReference.getResource().getIdElement().isAbsolute() || theReference.getResource().getIdElement().getValueAsString().startsWith("urn:")) { return false; } + // Check if the reference is a FHIR resource resolvable on the local server + // TODO Fix Hack: check for all resource types, not just Patient + if (theReference.getResource().getIdElement().getValueAsString().startsWith("Patient/")) { + return false; + } return true; }