From ddf3b72f2d90182d946df9c6b09bcf8bad08b109 Mon Sep 17 00:00:00 2001 From: James Agnew Date: Wed, 21 Jun 2023 13:02:40 -0400 Subject: [PATCH] Encode non-resource types (#5003) * Encode non-resource types * Add changelog * Compile fix --- .../ca/uhn/fhir/context/ModelScanner.java | 4 +- .../java/ca/uhn/fhir/parser/BaseParser.java | 57 +++- .../main/java/ca/uhn/fhir/parser/IParser.java | 47 +++ .../java/ca/uhn/fhir/parser/JsonParser.java | 13 +- .../java/ca/uhn/fhir/parser/RDFParser.java | 2 + .../java/ca/uhn/fhir/parser/XmlParser.java | 17 + .../6_8_0/5003-add-non-resource-encode.yaml | 5 + .../ca/uhn/fhir/parser/JsonParserR4Test.java | 43 +++ .../ca/uhn/fhir/parser/RDFParserR4Test.java | 295 +++--------------- .../ca/uhn/fhir/parser/XmlParserR4Test.java | 44 +++ 10 files changed, 265 insertions(+), 262 deletions(-) create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5003-add-non-resource-encode.yaml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index fd69482fc64..b424d208bfb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -223,7 +223,7 @@ class ModelScanner { private void scanBlock(Class theClass) { ourLog.debug("Scanning resource block class: {}", theClass.getName()); - String resourceName = theClass.getCanonicalName(); + String blockName = theClass.getSimpleName(); // Just in case someone messes up when upgrading from DSTU2 if (myContext.getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) { @@ -232,7 +232,7 @@ class ModelScanner { } } - RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(resourceName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions); + RuntimeResourceBlockDefinition blockDef = new RuntimeResourceBlockDefinition(blockName, theClass, isStandardType(theClass), myContext, myClassToElementDefinitions); blockDef.populateScanAlso(myScanAlso); myClassToElementDefinitions.put(theClass, blockDef); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java index 6f3d8406a6e..5b3dedfa4ad 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/BaseParser.java @@ -100,7 +100,7 @@ public abstract class BaseParser implements IParser { private FhirTerser.ContainedResources myContainedResources; private boolean myEncodeElementsAppliesToChildResourcesOnly; - private FhirContext myContext; + private final FhirContext myContext; private List myDontEncodeElements; private List myEncodeElements; private Set myEncodeElementsAppliesToResourceTypes; @@ -262,6 +262,10 @@ public abstract class BaseParser implements IParser { protected abstract void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException; + protected void doEncodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException { + throw new InternalErrorException(Msg.code(2363) + "This parser does not support encoding non-resource values"); + } + protected abstract T doParseResource(Class theResourceType, Reader theReader) throws DataFormatException; @Override @@ -270,7 +274,7 @@ public abstract class BaseParser implements IParser { try { encodeResourceToWriter(theResource, stringWriter); } catch (IOException e) { - throw new Error(Msg.code(1828) + "Encountered IOException during write to string - This should not happen!"); + throw new Error(Msg.code(1828) + "Encountered IOException during write to string - This should not happen!", e); } return stringWriter.toString(); } @@ -278,10 +282,34 @@ public abstract class BaseParser implements IParser { @Override public final void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException { EncodeContext encodeContext = new EncodeContext(); - encodeResourceToWriter(theResource, theWriter, encodeContext); } + + @Override + public String encodeToString(IBase theElement) throws DataFormatException { + Writer stringWriter = new StringBuilderWriter(); + try { + encodeToWriter(theElement, stringWriter); + } catch (IOException e) { + throw new Error(Msg.code(2364) + "Encountered IOException during write to string - This should not happen!", e); + } + return stringWriter.toString(); + } + + @Override + public void encodeToWriter(IBase theElement, Writer theWriter) throws DataFormatException, IOException { + if (theElement instanceof IBaseResource) { + encodeResourceToWriter((IBaseResource) theElement, theWriter); + } else if (theElement instanceof IPrimitiveType) { + theWriter.write(((IPrimitiveType) theElement).getValueAsString()); + } else { + EncodeContext encodeContext = new EncodeContext(); + encodeToWriter(theElement, theWriter, encodeContext); + } + } + + protected void encodeResourceToWriter(IBaseResource theResource, Writer theWriter, EncodeContext theEncodeContext) throws IOException { Validate.notNull(theResource, "theResource can not be null"); Validate.notNull(theWriter, "theWriter can not be null"); @@ -289,12 +317,11 @@ public abstract class BaseParser implements IParser { if (myContext.getVersion().getVersion() == FhirVersionEnum.R4B && theResource.getStructureFhirVersionEnum() == FhirVersionEnum.R5) { // TODO: remove once we've bumped the core lib version - } else - if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { + } else if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) { throw new IllegalArgumentException(Msg.code(1829) + "This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum()); } - String resourceName = myContext.getResourceType(theResource); + String resourceName = myContext.getElementDefinition(theResource.getClass()).getName(); theEncodeContext.pushPath(resourceName, true); doEncodeResourceToWriter(theResource, theWriter, theEncodeContext); @@ -302,6 +329,20 @@ public abstract class BaseParser implements IParser { theEncodeContext.popPath(); } + protected void encodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext) throws IOException { + Validate.notNull(theElement, "theElement can not be null"); + Validate.notNull(theWriter, "theWriter can not be null"); + Validate.notNull(theEncodeContext, "theEncodeContext can not be null"); + + BaseRuntimeElementDefinition def = myContext.getElementDefinition(theElement.getClass()); + String elementName = def.getName(); + theEncodeContext.pushPath(elementName, true); + + doEncodeToWriter(theElement, theWriter, theEncodeContext); + + theEncodeContext.popPath(); + } + private void filterCodingsWithNoCodeOrSystem(List tagList) { for (int i = 0; i < tagList.size(); i++) { if (isBlank(tagList.get(i).getCode()) && isBlank(tagList.get(i).getSystem())) { @@ -896,7 +937,7 @@ public abstract class BaseParser implements IParser { RuntimeChildChoiceDefinition choice = (RuntimeChildChoiceDefinition) nextChild; b.append(" - Expected one of: " + choice.getValidChildTypes()); } - throw new DataFormatException(Msg.code(1831) + b.toString()); + throw new DataFormatException(Msg.code(1831) + b); } throw new DataFormatException(Msg.code(1832) + nextChild + " has no child of type " + theType); } @@ -971,7 +1012,7 @@ public abstract class BaseParser implements IParser { if (myDef != null) { path.append(myDef.getElementName()); } - ourLog.trace(" * Next path: {}", path.toString()); + ourLog.trace(" * Next path: {}", path); } } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java index ae620f47541..dbd3fb801bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java @@ -25,6 +25,7 @@ import ca.uhn.fhir.context.ParserOptions; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.api.EncodingEnum; import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -46,10 +47,56 @@ import java.util.Set; */ public interface IParser { + /** + * Encodes a resource using the parser's given encoding format. + * + * @param theResource The resource to encode. Must not be null. + * @return A string representation of the encoding + * @throws DataFormatException If any invalid elements within the contents to be encoded prevent successful encoding. + */ String encodeResourceToString(IBaseResource theResource) throws DataFormatException; + /** + * Encodes a resource using the parser's given encoding format. + * + * @param theResource The resource to encode. Must not be null. + * @param theWriter The writer to write to. + * @throws DataFormatException If any invalid elements within the contents to be encoded prevent successful encoding. + */ void encodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException, DataFormatException; + /** + * Encodes any FHIR element to a string. + * If a {@link IBaseResource resource object} is passed in, the resource will be encoded using standard FHIR + * encoding rules. If a {@link org.hl7.fhir.instance.model.api.IPrimitiveType primitive datatype} is passed in, + * the string value of the primitive type is encoded. Any extensions on the primitive type are not encoded. + * If any other object is passed in, a fragment is encoded. The format of the fragment depends on the encoding: + *
    + *
  • JSON: The fragment is output as a simple JSON object, exactly as it would appear within an encoded resource.
  • + *
  • XML: The fragment is output as an XML element as it would appear within an encoded resource, however it is wrapped in an element called <element> in order to avoid producing a document with multiple root tags.
  • + *
  • RDF/Turtle: This mode is not supported and will throw an {@link ca.uhn.fhir.rest.server.exceptions.InternalErrorException}
  • + *
+ * + * @since 6.8.0 + */ + String encodeToString(IBase theElement) throws DataFormatException; + + /** + * Encodes any FHIR element to a writer. + * If a {@link IBaseResource resource object} is passed in, the resource will be encoded using standard FHIR + * encoding rules. If a {@link org.hl7.fhir.instance.model.api.IPrimitiveType primitive datatype} is passed in, + * the string value of the primitive type is encoded. Any extensions on the primitive type are not encoded. + * If any other object is passed in, a fragment is encoded. The format of the fragment depends on the encoding: + *
    + *
  • JSON: The fragment is output as a simple JSON object, exactly as it would appear within an encoded resource.
  • + *
  • XML: The fragment is output as an XML element as it would appear within an encoded resource, however it is wrapped in an element called <element> in order to avoid producing a document with multiple root tags.
  • + *
  • RDF/Turtle: This mode is not supported and will throw an {@link ca.uhn.fhir.rest.server.exceptions.InternalErrorException}
  • + *
+ * + * @since 6.8.0 + */ + void encodeToWriter(IBase theElement, Writer theWriter) throws DataFormatException, IOException; + /** * If not set to null (as is the default) this ID will be used as the ID in any * resources encoded by this parser diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java index 79994c572f5..e0fefa26aea 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java @@ -210,6 +210,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { eventWriter.close(); } + @Override + protected void doEncodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException { + BaseJsonLikeWriter eventWriter = createJsonWriter(theWriter); + eventWriter.beginObject(); + encodeCompositeElementToStreamWriter(null, null, theElement, eventWriter, false, null, theEncodeContext); + eventWriter.endObject(); + eventWriter.close(); + } + @Override public T doParseResource(Class theResourceType, Reader theReader) { JsonLikeStructure jsonStructure = new JacksonStructure(); @@ -1446,9 +1455,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser { // Undeclared extensions extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null, theEncodeContext, theContainedResource); // Declared extensions - if (def != null) { - extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent); - } + extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent); boolean haveContent = false; if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) { haveContent = true; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java index 53c2ce841b5..2a814c510ad 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/RDFParser.java @@ -33,6 +33,7 @@ import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.narrative.INarrativeGenerator; import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.rdf.RDFUtil; import org.apache.commons.lang3.StringUtils; import org.apache.jena.datatypes.xsd.XSDDatatype; @@ -59,6 +60,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.INarrative; import org.hl7.fhir.instance.model.api.IPrimitiveType; +import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Arrays; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java index 0adebc13664..71ae7534831 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java @@ -71,6 +71,7 @@ import javax.xml.stream.events.EntityReference; import javax.xml.stream.events.Namespace; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; +import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.ArrayList; @@ -140,6 +141,22 @@ public class XmlParser extends BaseParser { } } + @Override + protected void doEncodeToWriter(IBase theElement, Writer theWriter, EncodeContext theEncodeContext) throws IOException, DataFormatException { + XMLStreamWriter eventWriter; + try { + eventWriter = createXmlWriter(theWriter); + + eventWriter.writeStartElement("element"); + encodeCompositeElementToStreamWriter(null, theElement, eventWriter, false, new CompositeChildElement(null, theEncodeContext), theEncodeContext); + eventWriter.writeEndElement(); + + eventWriter.flush(); + } catch (XMLStreamException e) { + throw new ConfigurationException(Msg.code(2365) + "Failed to initialize STaX event factory", e); + } + } + @Override public T doParseResource(Class theResourceType, Reader theReader) { XMLEventReader streamReader = createStreamReader(theReader); diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5003-add-non-resource-encode.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5003-add-non-resource-encode.yaml new file mode 100644 index 00000000000..3f65b27bbe9 --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5003-add-non-resource-encode.yaml @@ -0,0 +1,5 @@ +--- +type: add +issue: 5003 +title: "The IParser interface now has an additional pair of methods called `encodeToString(IBase)` and `encodeToWriter(IBase, Writer)` that + can be used to encode fragments of resources, such as datatypes or infrastructure elements." 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 3566d03f555..6dd4e7893df 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 @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Composition; import org.hl7.fhir.r4.model.DateTimeType; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Device; import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.Encounter; @@ -1158,6 +1159,48 @@ public class JsonParserR4Test extends BaseTest { } + + @Test + public void testEncodeToString_PrimitiveDataType() { + DecimalType object = new DecimalType("123.456000"); + String expected = "123.456000"; + String actual = ourCtx.newJsonParser().encodeToString(object); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_Resource() { + Patient p = new Patient(); + p.setId("Patient/123"); + p.setActive(true); + String expected = "{\"resourceType\":\"Patient\",\"id\":\"123\",\"active\":true}"; + String actual = ourCtx.newJsonParser().encodeToString(p); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_GeneralPurposeDataType() { + HumanName name = new HumanName(); + name.setFamily("Simpson").addGiven("Homer").addGiven("Jay"); + name.addExtension("http://foo", new StringType("bar")); + + String expected = "{\"extension\":[{\"url\":\"http://foo\",\"valueString\":\"bar\"}],\"family\":\"Simpson\",\"given\":[\"Homer\",\"Jay\"]}"; + String actual = ourCtx.newJsonParser().encodeToString(name); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_BackboneElement() { + Patient.PatientCommunicationComponent communication = new Patient().addCommunication(); + communication.setPreferred(true); + communication.getLanguage().setText("English"); + + String expected = "{\"language\":{\"text\":\"English\"},\"preferred\":true}"; + String actual = ourCtx.newJsonParser().encodeToString(communication); + assertEquals(expected, actual); + } + + @Test public void testPreCommentsToFhirComments() { final Patient patient = new Patient(); diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserR4Test.java index 068fa36c9c0..0d20454ddae 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserR4Test.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/RDFParserR4Test.java @@ -1,10 +1,13 @@ package ca.uhn.fhir.parser; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.TestUtil; import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Encounter; +import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.Medication; import org.hl7.fhir.r4.model.MedicationDispense; import org.hl7.fhir.r4.model.MedicationRequest; @@ -13,6 +16,7 @@ import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Practitioner; import org.hl7.fhir.r4.model.QuestionnaireResponse; import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -23,269 +27,62 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; -@Disabled public class RDFParserR4Test { - private static final Logger ourLog = LoggerFactory.getLogger(RDFParserR4Test.class); - private static FhirContext ourCtx = FhirContext.forR4(); + private static final FhirContext ourCtx = FhirContext.forR4Cached(); - /* - private Bundle createBundleWithPatient() { - Bundle b = new Bundle(); - b.setId("BUNDLEID"); - b.getMeta().addProfile("http://FOO"); + + @Test + public void testEncodeToString_PrimitiveDataType() { + DecimalType object = new DecimalType("123.456000"); + String expected = "123.456000"; + String actual = ourCtx.newRDFParser().encodeToString(object); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_Resource() { Patient p = new Patient(); - p.setId("PATIENTID"); - p.getMeta().addProfile("http://BAR"); - p.addName().addGiven("GIVEN"); - b.addEntry().setResource(p); - return b; - } - */ + p.setId("Patient/123"); + p.setActive(true); + String expected = """ + @prefix fhir: . + @prefix rdf: . + @prefix rdfs: . + @prefix sct: . + @prefix xsd: . + + + rdf:type fhir:Patient ; + fhir:Patient.active [ fhir:value true ] ; + fhir:Resource.id [ fhir:value "123" ] ; + fhir:nodeRole fhir:treeRoot . + """; - @Test - public void testDontStripVersions() { - FhirContext ctx = FhirContext.forR4(); - ctx.getParserOptions().setDontStripVersionsFromReferencesAtPaths("QuestionnaireResponse.questionnaire"); - - QuestionnaireResponse qr = new QuestionnaireResponse(); - qr.getQuestionnaireElement().setValueAsString("Questionnaire/123/_history/456"); - - String output = ctx.newRDFParser().setPrettyPrint(true).encodeResourceToString(qr); - ourLog.info(output); - - assertThat(output, containsString("\"Questionnaire/123/_history/456\"")); + String actual = ourCtx.newRDFParser().encodeToString(p); + assertEquals(expected, actual); } @Test - public void testDuplicateContainedResourcesNotOutputtedTwice() { - MedicationDispense md = new MedicationDispense(); + public void testEncodeToString_GeneralPurposeDataType() { + HumanName name = new HumanName(); + name.setFamily("Simpson").addGiven("Homer").addGiven("Jay"); + name.addExtension("http://foo", new StringType("bar")); - 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.newRDFParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertNotEquals(-1, idx); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); - } - - /** - * 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.newRDFParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertNotEquals(-1, idx); - - 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.newRDFParser().setPrettyPrint(true).encodeResourceToString(md); - ourLog.info(encoded); - - int idx = encoded.indexOf("\"Medication\""); - assertNotEquals(-1, idx); - - idx = encoded.indexOf("\"Medication\"", idx + 1); - assertEquals(-1, idx); + assertEquals("HAPI-2363: This parser does not support encoding non-resource values", + assertThrows(InternalErrorException.class, ()->ourCtx.newRDFParser().encodeToString(name)).getMessage()); } @Test - public void testEncodeAndParseUnicodeCharacterInNarrative() { - Patient p = new Patient(); - p.getText().getDiv().setValueAsString("
Copy © 1999
"); - String encoded = ourCtx.newRDFParser().encodeResourceToString(p); - ourLog.info(encoded); + public void testEncodeToString_BackboneElement() { + Patient.PatientCommunicationComponent communication = new Patient().addCommunication(); + communication.setPreferred(true); + communication.getLanguage().setText("English"); - p = (Patient) ourCtx.newRDFParser().parseResource(encoded); - assertEquals("
Copy © 1999
", p.getText().getDivAsString()); - } - - - @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.newRDFParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newRDFParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals("#2", 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.newRDFParser().setPrettyPrint(true).encodeResourceToString(obs); - ourLog.info(encoded); - - obs = ourCtx.newRDFParser().parseResource(Observation.class, encoded); - assertEquals("#1", obs.getContained().get(0).getId()); - assertEquals("#2", 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.newRDFParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newRDFParser().parseResource(MedicationRequest.class, encoded); - - mr.setMedication(new Reference(new Medication().setStatus(Medication.MedicationStatus.ACTIVE))); - encoded = ourCtx.newRDFParser().setPrettyPrint(true).encodeResourceToString(mr); - ourLog.info(encoded); - mr = ourCtx.newRDFParser().parseResource(MedicationRequest.class, encoded); - - assertEquals("#2", mr.getContained().get(0).getId()); - assertEquals("#1", mr.getContained().get(1).getId()); - } - - - /** - * Test that long JSON strings don't get broken up - */ - @Test - public void testNoBreakInLongString() { - String longString = StringUtils.leftPad("", 100000, 'A'); - - Patient p = new Patient(); - p.addName().setFamily(longString); - String encoded = ourCtx.newRDFParser().setPrettyPrint(true).encodeResourceToString(p); - - assertThat(encoded, containsString(longString)); - } - - - @Test - public void testParseAndEncodeExtensionWithValueWithExtension() { - String input = "{\n" + - " \"resourceType\": \"Patient\",\n" + - " \"extension\": [\n" + - " {\n" + - " \"url\": \"https://purl.org/elab/fhir/network/StructureDefinition/1/BirthWeight\",\n" + - " \"_valueDecimal\": {\n" + - " \"extension\": [\n" + - " {\n" + - " \"url\": \"http://www.hl7.org/fhir/extension-data-absent-reason.html\",\n" + - " \"valueCoding\": {\n" + - " \"system\": \"http://hl7.org/fhir/ValueSet/birthweight\",\n" + - " \"code\": \"Underweight\",\n" + - " \"userSelected\": false\n" + - " }\n" + - " }\n" + - " ]\n" + - " }\n" + - " }\n" + - " ],\n" + - " \"identifier\": [\n" + - " {\n" + - " \"system\": \"https://purl.org/elab/fhir/network/StructureDefinition/1/EuroPrevallStudySubjects\",\n" + - " \"value\": \"1\"\n" + - " }\n" + - " ],\n" + - " \"gender\": \"female\"\n" + - "}"; - - IParser jsonParser = ourCtx.newRDFParser(); - IParser xmlParser = ourCtx.newXmlParser(); - jsonParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); - xmlParser.setDontEncodeElements(Sets.newHashSet("id", "meta")); - - Patient parsed = jsonParser.parseResource(Patient.class, input); - - ourLog.info(jsonParser.setPrettyPrint(true).encodeResourceToString(parsed)); - assertThat(xmlParser.encodeResourceToString(parsed), containsString("Underweight")); - assertThat(jsonParser.encodeResourceToString(parsed), containsString("Underweight")); - } - - @AfterAll - public static void afterClassClearContext() { - TestUtil.randomizeLocaleAndTimezone(); + assertEquals("HAPI-2363: This parser does not support encoding non-resource values", + assertThrows(InternalErrorException.class, ()->ourCtx.newRDFParser().encodeToString(communication)).getMessage()); } } 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 6352495d024..16af55d7ac6 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 @@ -14,8 +14,10 @@ import org.hl7.fhir.r4.model.Appointment; import org.hl7.fhir.r4.model.AuditEvent; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.DocumentReference; import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.HumanName; import org.hl7.fhir.r4.model.MessageHeader; import org.hl7.fhir.r4.model.Meta; import org.hl7.fhir.r4.model.Narrative; @@ -24,6 +26,7 @@ import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Patient; import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.StringType; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -197,6 +200,47 @@ public class XmlParserR4Test extends BaseTest { ourLog.info(encoded); } + @Test + public void testEncodeToString_PrimitiveDataType() { + DecimalType object = new DecimalType("123.456000"); + String expected = "123.456000"; + String actual = ourCtx.newXmlParser().encodeToString(object); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_Resource() { + Patient p = new Patient(); + p.setId("Patient/123"); + p.setActive(true); + String expected = ""; + String actual = ourCtx.newXmlParser().encodeToString(p); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_GeneralPurposeDataType() { + HumanName name = new HumanName(); + name.setFamily("Simpson").addGiven("Homer").addGiven("Jay"); + name.addExtension("http://foo", new StringType("bar")); + + String expected = ""; + String actual = ourCtx.newXmlParser().encodeToString(name); + assertEquals(expected, actual); + } + + @Test + public void testEncodeToString_BackboneElement() { + Patient.PatientCommunicationComponent communication = new Patient().addCommunication(); + communication.setPreferred(true); + communication.getLanguage().setText("English"); + + String expected = ""; + String actual = ourCtx.newXmlParser().encodeToString(communication); + assertEquals(expected, actual); + } + + /** * Ensure that a contained bundle doesn't cause a crash */