From 496d866f481dfba6053c099374233468f696e1be Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Sun, 28 Feb 2016 22:15:43 -0500 Subject: [PATCH] Fix #274 - Primitive elements with no value but an extension were sometimes not encoded correctly in XML, and sometimes not parsed correctly in JSON. --- .../java/ca/uhn/fhir/parser/BaseParser.java | 42 +++++++-- .../java/ca/uhn/fhir/parser/JsonParser.java | 88 +++++++++++++------ .../java/ca/uhn/fhir/parser/XmlParser.java | 51 ++++++----- .../model/api/IBaseHasExtensions.java | 6 +- .../model/api/IBaseHasModifierExtensions.java | 6 +- .../EmptyElementWithExtensionDstu2Test.java | 74 ++++++++++++++++ .../parser/EmptyElementWithExtensionTest.java | 28 ------ .../EmptyElementWithExtensionDstu3Test.java | 75 ++++++++++++++++ src/changes/changes.xml | 5 ++ 9 files changed, 282 insertions(+), 93 deletions(-) create mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu2Test.java delete mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionTest.java create mode 100644 hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu3Test.java 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 a4074bbeb65..17e467ae9fe 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 @@ -42,6 +42,8 @@ import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBaseCoding; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions; import org.hl7.fhir.instance.model.api.IBaseMetaType; import org.hl7.fhir.instance.model.api.IBaseReference; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -64,6 +66,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ISupportsUndeclaredExtensions; import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; import ca.uhn.fhir.model.api.Tag; import ca.uhn.fhir.model.api.TagList; @@ -549,7 +552,7 @@ public abstract class BaseParser implements IParser { protected List preProcessValues(BaseRuntimeChildDefinition metaChildUncast, IBaseResource theResource, List theValues) { if (myContext.getVersion().getVersion().isRi()) { - + /* * If we're encoding the meta tag, we do some massaging of the meta values before * encoding. Buf if there is no meta element at all, we create one since we're possibly going to be @@ -562,36 +565,36 @@ public abstract class BaseParser implements IParser { theValues = Collections.singletonList(newType); } } - + if (theValues.size() == 1 && theValues.get(0) instanceof IBaseMetaType) { - + IBaseMetaType metaValue = (IBaseMetaType) theValues.get(0); try { metaValue = (IBaseMetaType) metaValue.getClass().getMethod("copy").invoke(metaValue); } catch (Exception e) { throw new InternalErrorException("Failed to duplicate meta", e); } - + if (isBlank(metaValue.getVersionId())) { if (theResource.getIdElement().hasVersionIdPart()) { metaValue.setVersionId(theResource.getIdElement().getVersionIdPart()); } } - + filterCodingsWithNoCodeOrSystem(metaValue.getTag()); filterCodingsWithNoCodeOrSystem(metaValue.getSecurity()); - + if (shouldAddSubsettedTag()) { IBaseCoding coding = metaValue.addTag(); coding.setCode(Constants.TAG_SUBSETTED_CODE); coding.setSystem(Constants.TAG_SUBSETTED_SYSTEM); coding.setDisplay(subsetDescription()); } - + return Collections.singletonList(metaValue); } } - + return theValues; } @@ -688,6 +691,28 @@ public abstract class BaseParser implements IParser { return securityLabels; } + static boolean hasExtensions(IBase theElement) { + if (theElement instanceof ISupportsUndeclaredExtensions) { + ISupportsUndeclaredExtensions res = (ISupportsUndeclaredExtensions) theElement; + if (res.getUndeclaredExtensions().size() > 0 || res.getUndeclaredModifierExtensions().size() > 0) { + return true; + } + } + if (theElement instanceof IBaseHasExtensions) { + IBaseHasExtensions res = (IBaseHasExtensions) theElement; + if (res.hasExtension()) { + return true; + } + } + if (theElement instanceof IBaseHasModifierExtensions) { + IBaseHasModifierExtensions res = (IBaseHasModifierExtensions) theElement; + if (res.hasModifierExtension()) { + return true; + } + } + return false; + } + protected class CompositeChildElement { private final BaseRuntimeChildDefinition myDef; private final CompositeChildElement myParent; @@ -711,7 +736,6 @@ public abstract class BaseParser implements IParser { } - public CompositeChildElement(RuntimeResourceDefinition theResDef) { myResDef = theResDef; myDef = null; 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 22aa0c20442..55d032b93d2 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 @@ -216,7 +216,7 @@ public class JsonParser extends BaseParser implements IParser { try { JsonReader reader = Json.createReader(theReader); JsonObject object = reader.readObject(); - + JsonValue resourceTypeObj = object.get("resourceType"); assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType"); String resourceType = ((JsonString) resourceTypeObj).getString(); @@ -440,7 +440,8 @@ public class JsonParser extends BaseParser implements IParser { @Override public String toString() { return value.getValueAsString(); - }}; + } + }; if (theChildName != null) { theWriter.write(theChildName, decimalValue); } else { @@ -549,7 +550,7 @@ public class JsonParser extends BaseParser implements IParser { private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonGenerator theEventWriter, List theChildren, boolean theContainedResource, CompositeChildElement theParent) throws IOException { - + for (CompositeChildElement nextChildElem : super.compositeChildIterator(theChildren, theContainedResource, theParent)) { BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); @@ -560,7 +561,7 @@ public class JsonParser extends BaseParser implements IParser { if (theResource instanceof IResource) { narr = ((IResource) theResource).getText(); } else if (theResource instanceof IDomainResource) { - narr = ((IDomainResource)theResource).getText(); + narr = ((IDomainResource) theResource).getText(); } else { narr = null; } @@ -706,7 +707,7 @@ public class JsonParser extends BaseParser implements IParser { haveContent = true; heldModExts = modifierExtensions.get(i); } - + ArrayList nextComments; if (comments.size() > i) { nextComments = comments.get(i); @@ -746,7 +747,7 @@ public class JsonParser extends BaseParser implements IParser { throws IOException, DataFormatException { writeCommentsPreAndPost(theNextValue, theEventWriter); - + extractAndWriteExtensionsAsDirectChild(theNextValue, theEventWriter, resDef, theResDef, theResource, null); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, resDef.getExtensions(), theContainedResource, theParent); encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, resDef.getChildren(), theContainedResource, theParent); @@ -756,13 +757,13 @@ public class JsonParser extends BaseParser implements IParser { if (theNextValue.hasFormatComment()) { theEventWriter.writeStartArray("fhir_comments"); List pre = theNextValue.getFormatCommentsPre(); - if (pre.isEmpty()==false) { + if (pre.isEmpty() == false) { for (String next : pre) { theEventWriter.write(next); } } List post = theNextValue.getFormatCommentsPost(); - if (post.isEmpty()==false) { + if (post.isEmpty() == false) { for (String next : post) { theEventWriter.write(next); } @@ -773,21 +774,21 @@ public class JsonParser extends BaseParser implements IParser { private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonGenerator theEventWriter, String theObjectNameOrNull, boolean theContainedResource) throws IOException { IIdType resourceId = null; -// if (theResource instanceof IResource) { -// IResource res = (IResource) theResource; -// if (StringUtils.isNotBlank(res.getId().getIdPart())) { -// if (theContainedResource) { -// resourceId = res.getId().getIdPart(); -// } else if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { -// resourceId = res.getId().getIdPart(); -// } -// } -// } else if (theResource instanceof IAnyResource) { -// IAnyResource res = (IAnyResource) theResource; -// if (/* theContainedResource && */StringUtils.isNotBlank(res.getIdElement().getIdPart())) { -// resourceId = res.getIdElement().getIdPart(); -// } -// } + // if (theResource instanceof IResource) { + // IResource res = (IResource) theResource; + // if (StringUtils.isNotBlank(res.getId().getIdPart())) { + // if (theContainedResource) { + // resourceId = res.getId().getIdPart(); + // } else if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) { + // resourceId = res.getId().getIdPart(); + // } + // } + // } else if (theResource instanceof IAnyResource) { + // IAnyResource res = (IAnyResource) theResource; + // if (/* theContainedResource && */StringUtils.isNotBlank(res.getIdElement().getIdPart())) { + // resourceId = res.getIdElement().getIdPart(); + // } + // } if (StringUtils.isNotBlank(theResource.getIdElement().getIdPart())) { resourceId = theResource.getIdElement(); @@ -1183,8 +1184,12 @@ public class JsonParser extends BaseParser implements IParser { private void parseChildren(JsonObject theObject, ParserState theState) { String elementId = null; - + Set keySet = theObject.keySet(); + + int allUnderscoreNames = 0; + int handledUnderscoreNames = 0; + for (String nextName : keySet) { if ("resourceType".equals(nextName)) { continue; @@ -1217,12 +1222,16 @@ public class JsonParser extends BaseParser implements IParser { parseFhirComments(theObject.get(nextName), theState); continue; } else if (nextName.charAt(0) == '_') { + allUnderscoreNames++; continue; } JsonValue nextVal = theObject.get(nextName); String alternateName = '_' + nextName; JsonValue alternateVal = theObject.get(alternateName); + if (alternateVal != null) { + handledUnderscoreNames++; + } parseChildren(theState, nextName, nextVal, alternateVal, alternateName); @@ -1236,13 +1245,36 @@ public class JsonParser extends BaseParser implements IParser { ((IBaseResource) object).getIdElement().setValue(elementId); } } + + /* + * This happens if an element has an extension but no actual value. I.e. + * if a resource has a "_status" element but no corresponding "status" + * element. This could be used to handle a null value with an extension + * for example. + */ + if (allUnderscoreNames > handledUnderscoreNames) { + for (String alternateName : keySet) { + if (alternateName.startsWith("_") && alternateName.length() > 1) { + JsonValue nextValue = theObject.get(alternateName); + if (nextValue.getValueType() == ValueType.OBJECT) { + String nextName = alternateName.substring(1); + if (theObject.get(nextName) == null) { + theState.enteringNewElement(null, nextName); + parseAlternates(nextValue, theState, alternateName); + theState.endingElement(); + } + } + } + } + } + } private void parseFhirComments(JsonValue theObject, ParserState theState) { if (theObject.getValueType() == ValueType.ARRAY) { - for (JsonValue nextComment : ((JsonArray)theObject)) { + for (JsonValue nextComment : ((JsonArray) theObject)) { if (nextComment.getValueType() == ValueType.STRING) { - String commentText = ((JsonString)nextComment).getString(); + String commentText = ((JsonString) nextComment).getString(); if (commentText != null) { theState.commentPre(commentText); } @@ -1550,7 +1582,7 @@ public class JsonParser extends BaseParser implements IParser { writeUndeclaredExtInDstu1Format(theResDef, theResource, theEventWriter, myUndeclaredExtension); } else { theEventWriter.writeStartObject(); - + writeCommentsPreAndPost(myValue, theEventWriter); theEventWriter.write("url", myDef.getExtensionUrl()); @@ -1572,7 +1604,7 @@ public class JsonParser extends BaseParser implements IParser { String extensionUrl = ext.getUrl(); theEventWriter.writeStartObject(); - + writeCommentsPreAndPost(myUndeclaredExtension, theEventWriter); theEventWriter.write("url", extensionUrl); 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 a34270b309b..d83af09190f 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 @@ -95,7 +95,8 @@ import ca.uhn.fhir.util.PrettyPrintWriterWrapper; import ca.uhn.fhir.util.XmlUtil; /** - * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use {@link FhirContext#newXmlParser()} to get an instance. + * This class is the FHIR XML parser/encoder. Users should not interact with this class directly, but should use + * {@link FhirContext#newXmlParser()} to get an instance. */ public class XmlParser extends BaseParser implements IParser { @@ -114,7 +115,8 @@ public class XmlParser extends BaseParser implements IParser { private boolean myPrettyPrint; /** - * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke {@link FhirContext#newXmlParser()}. + * Do not use this constructor, the recommended way to obtain a new instance of the XML parser is to invoke + * {@link FhirContext#newXmlParser()}. * * @param theParserErrorHandler */ @@ -198,7 +200,7 @@ public class XmlParser extends BaseParser implements IParser { try { List heldComments = new ArrayList(1); - + while (streamReader.hasNext()) { XMLEvent nextEvent = streamReader.nextEvent(); try { @@ -225,7 +227,7 @@ public class XmlParser extends BaseParser implements IParser { String elementName = elem.getName().getLocalPart(); parserState.enteringNewElement(namespaceURI, elementName); } - + if (!heldComments.isEmpty()) { for (String next : heldComments) { parserState.commentPre(next); @@ -272,7 +274,7 @@ public class XmlParser extends BaseParser implements IParser { break; } } - + parserState.xmlEvent(nextEvent); } catch (DataFormatException e) { @@ -524,9 +526,11 @@ public class XmlParser extends BaseParser implements IParser { case PRIMITIVE_DATATYPE: { IPrimitiveType pd = (IPrimitiveType) theElement; String value = pd.getValueAsString(); - if (value != null) { + if (value != null || super.hasExtensions(pd)) { theEventWriter.writeStartElement(childName); - theEventWriter.writeAttribute("value", value); + if (value != null) { + theEventWriter.writeAttribute("value", value); + } encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); theEventWriter.writeEndElement(); } @@ -623,8 +627,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List theChildren, - boolean theContainedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementChildrenToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, List theChildren, boolean theContainedResource, CompositeChildElement theParent) + throws XMLStreamException, DataFormatException { for (CompositeChildElement nextChildElem : super.compositeChildIterator(theChildren, theContainedResource, theParent)) { BaseRuntimeChildDefinition nextChild = nextChildElem.getDef(); @@ -635,7 +639,7 @@ public class XmlParser extends BaseParser implements IParser { if (theResource instanceof IResource) { narr = ((IResource) theResource).getText(); } else if (theResource instanceof IDomainResource) { - narr = ((IDomainResource)theResource).getText(); + narr = ((IDomainResource) theResource).getText(); } else { narr = null; } @@ -652,8 +656,7 @@ public class XmlParser extends BaseParser implements IParser { } if (nextChild instanceof RuntimeChildContainedResources) { - encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, - nextChildElem); + encodeChildElementToStreamWriter(theResource, theEventWriter, null, nextChild.getChildNameByDatatype(null), nextChild.getChildElementDefinitionByDatatype(null), null, theContainedResource, nextChildElem); } else { List values = nextChild.getAccessor().getValues(theElement); @@ -700,8 +703,8 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, - boolean theIncludedResource, CompositeChildElement theParent) throws XMLStreamException, DataFormatException { + private void encodeCompositeElementToStreamWriter(IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition theElementDefinition, boolean theIncludedResource, CompositeChildElement theParent) + throws XMLStreamException, DataFormatException { encodeExtensionsIfPresent(theResource, theEventWriter, theElement, theIncludedResource); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getExtensions(), theIncludedResource, theParent); encodeCompositeElementChildrenToStreamWriter(theResource, theElement, theEventWriter, theElementDefinition.getChildren(), theIncludedResource, theParent); @@ -740,10 +743,10 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, - BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { + private void encodeResourceToStreamWriterInDstu2Format(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, XMLStreamWriter theEventWriter, BaseRuntimeElementCompositeDefinition resDef, boolean theIncludedResource) throws XMLStreamException, DataFormatException { /* - * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy way to make that happen, but hopefully this won't matter as much once we use the HL7 + * DSTU2 requires extensions to come in a specific spot within the encoded content - This is a bit of a messy way + * to make that happen, but hopefully this won't matter as much once we use the HL7 * structures */ @@ -812,7 +815,7 @@ public class XmlParser extends BaseParser implements IParser { writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart()); writeCommentsPost(theEventWriter, theResourceId); } - + encodeCompositeElementToStreamWriter(theResource, theResource, theEventWriter, resDef, theContainedResource, new CompositeChildElement(resDef)); } else { @@ -827,7 +830,7 @@ public class XmlParser extends BaseParser implements IParser { writeOptionalTagWithValue(theEventWriter, "id", theResourceId.getIdPart()); writeCommentsPost(theEventWriter, theResourceId); } - + InstantDt updated = (InstantDt) resource.getResourceMetadata().get(ResourceMetadataKeyEnum.UPDATED); IdDt resourceId = resource.getId(); String versionIdPart = resourceId.getVersionIdPart(); @@ -934,15 +937,14 @@ public class XmlParser extends BaseParser implements IParser { } } - private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) - throws XMLStreamException, DataFormatException { + private void encodeUndeclaredExtensions(IBaseResource theResource, XMLStreamWriter theWriter, List> theExtensions, String tagName, boolean theIncludedResource) throws XMLStreamException, DataFormatException { for (IBaseExtension next : theExtensions) { if (next == null || (ElementUtil.isEmpty(next.getValue()) && next.getExtension().isEmpty())) { continue; } writeCommentsPre(theWriter, next); - + theWriter.writeStartElement(tagName); String url = next.getUrl(); @@ -973,7 +975,7 @@ public class XmlParser extends BaseParser implements IParser { encodeExtensionsIfPresent(theResource, theWriter, next, theIncludedResource); theWriter.writeEndElement(); - + writeCommentsPost(theWriter, next); } @@ -1109,7 +1111,8 @@ public class XmlParser extends BaseParser implements IParser { } /** - * This is just to work around the fact that casting java.util.List to java.util.List> seems to be + * This is just to work around the fact that casting java.util.List to + * java.util.List> seems to be * rejected by the compiler some of the time. */ private > List> toBaseExtensionList(final List theList) { diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java index 153089275a8..a819024498b 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasExtensions.java @@ -24,8 +24,10 @@ import java.util.List; public interface IBaseHasExtensions { - public List> getExtension(); + IBaseExtension addExtension(); - public IBaseExtension addExtension(); + List> getExtension(); + + boolean hasExtension(); } diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java index f5888a75bfa..a002f200d94 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IBaseHasModifierExtensions.java @@ -24,8 +24,10 @@ import java.util.List; public interface IBaseHasModifierExtensions { - public List> getModifierExtension(); + public IBaseExtension addModifierExtension(); - public IBaseExtension addModifierExtension(); + public List> getModifierExtension(); + + boolean hasModifierExtension(); } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu2Test.java new file mode 100644 index 00000000000..cfd226283dc --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu2Test.java @@ -0,0 +1,74 @@ +package ca.uhn.fhir.parser; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.dstu2.composite.CodingDt; +import ca.uhn.fhir.model.dstu2.resource.Observation; +import ca.uhn.fhir.model.primitive.StringDt; + +/** + * Created by Bill de Beaubien on 12/20/2015. + */ +public class EmptyElementWithExtensionDstu2Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmptyElementWithExtensionDstu2Test.class); + private static FhirContext ctx = FhirContext.forDstu2(); + + @Test + public void testNullFlavorCompositeJson() throws Exception { + Observation observation = new Observation(); + observation.getCode().addCoding().addUndeclaredExtension(false, "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringDt("UNK")); + IParser parser = ctx.newJsonParser().setPrettyPrint(true); + String json = parser.encodeResourceToString(observation); + + ourLog.info(json); + + observation = (Observation) parser.parseResource(json); + assertEquals(1, observation.getCode().getCoding().get(0).getUndeclaredExtensions().size()); + } + + @Test + public void testNullFlavorCompositeXml() throws Exception { + Observation observation = new Observation(); + observation.getCode().addCoding().addUndeclaredExtension(false, "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringDt("UNK")); + IParser parser = ctx.newXmlParser().setPrettyPrint(true); + String xml = parser.encodeResourceToString(observation); + + ourLog.info(xml); + + observation = (Observation) parser.parseResource(xml); + assertEquals(1, observation.getCode().getCoding().get(0).getUndeclaredExtensions().size()); + } + + @Test + public void testNullFlavorPrimitiveJson() throws Exception { + Observation observation = new Observation(); + observation.getCode().getCoding().add(new CodingDt().setSystem("http://loinc.org").setCode("3141-9")); + observation.getStatusElement().addUndeclaredExtension(false, "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringDt("UNK")); + IParser parser = ctx.newJsonParser().setPrettyPrint(true); + String json = parser.encodeResourceToString(observation); + + ourLog.info(json); + + observation = (Observation) parser.parseResource(json); + assertEquals(1, observation.getStatusElement().getUndeclaredExtensions().size()); + } + + @Test + public void testNullFlavorPrimitiveXml() throws Exception { + Observation observation = new Observation(); + observation.getCode().getCoding().add(new CodingDt().setSystem("http://loinc.org").setCode("3141-9")); + observation.getStatusElement().addUndeclaredExtension(false, "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringDt("UNK")); + IParser parser = ctx.newXmlParser().setPrettyPrint(true); + String xml = parser.encodeResourceToString(observation); + + ourLog.info(xml); + + observation = (Observation) parser.parseResource(xml); + assertEquals(1, observation.getStatusElement().getUndeclaredExtensions().size()); + } + +} diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionTest.java deleted file mode 100644 index 3ed34de319d..00000000000 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package ca.uhn.fhir.parser; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.composite.CodingDt; -import ca.uhn.fhir.model.dstu2.resource.Observation; -import ca.uhn.fhir.model.primitive.StringDt; -import org.junit.Ignore; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -/** - * Created by Bill de Beaubien on 12/20/2015. - */ -public class EmptyElementWithExtensionTest { - @Ignore - @Test - public void testNullFlavor() throws Exception { - Observation observation = new Observation(); - observation.getCode().getCoding().add(new CodingDt().setSystem("http://loinc.org").setCode("3141-9")); - observation.getStatusElement().addUndeclaredExtension(false, "http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringDt("UNK")); - FhirContext ctx = FhirContext.forDstu2(); - IParser parser = ctx.newXmlParser().setPrettyPrint(true); - String xml = parser.encodeResourceToString(observation); - observation = (Observation) parser.parseResource(xml); - assertEquals(1, observation.getStatusElement().getUndeclaredExtensions().size()); - } -} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu3Test.java new file mode 100644 index 00000000000..cecf7b801f5 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/EmptyElementWithExtensionDstu3Test.java @@ -0,0 +1,75 @@ +package ca.uhn.fhir.parser; + +import static org.junit.Assert.assertEquals; + +import org.hl7.fhir.dstu3.model.Coding; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.Observation; +import org.hl7.fhir.dstu3.model.StringType; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; + +/** + * Created by Bill de Beaubien on 12/20/2015. + */ +public class EmptyElementWithExtensionDstu3Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmptyElementWithExtensionDstu3Test.class); + private static FhirContext ctx = FhirContext.forDstu3(); + + @Test + public void testNullFlavorCompositeJson() throws Exception { + Observation observation = new Observation(); + observation.getCode().addCoding().addExtension(new Extension("http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringType("UNK"))); + IParser parser = ctx.newJsonParser().setPrettyPrint(true); + String json = parser.encodeResourceToString(observation); + + ourLog.info(json); + + observation = (Observation) parser.parseResource(json); + assertEquals(1, observation.getCode().getCoding().get(0).getExtension().size()); + } + + @Test + public void testNullFlavorCompositeXml() throws Exception { + Observation observation = new Observation(); + observation.getCode().addCoding().addExtension(new Extension("http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringType("UNK"))); + IParser parser = ctx.newXmlParser().setPrettyPrint(true); + String xml = parser.encodeResourceToString(observation); + + ourLog.info(xml); + + observation = (Observation) parser.parseResource(xml); + assertEquals(1, observation.getCode().getCoding().get(0).getExtension().size()); + } + + @Test + public void testNullFlavorPrimitiveJson() throws Exception { + Observation observation = new Observation(); + observation.getCode().getCoding().add(new Coding().setSystem("http://loinc.org").setCode("3141-9")); + observation.getStatusElement().addExtension(new Extension("http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringType("UNK"))); + IParser parser = ctx.newJsonParser().setPrettyPrint(true); + String json = parser.encodeResourceToString(observation); + + ourLog.info(json); + + observation = (Observation) parser.parseResource(json); + assertEquals(1, observation.getStatusElement().getExtension().size()); + } + + @Test + public void testNullFlavorPrimitiveXml() throws Exception { + Observation observation = new Observation(); + observation.getCode().getCoding().add(new Coding().setSystem("http://loinc.org").setCode("3141-9")); + observation.getStatusElement().addExtension(new Extension("http://hl7.org/fhir/StructureDefinition/iso21090-nullFlavor", new StringType("UNK"))); + IParser parser = ctx.newXmlParser().setPrettyPrint(true); + String xml = parser.encodeResourceToString(observation); + + ourLog.info(xml); + + observation = (Observation) parser.parseResource(xml); + assertEquals(1, observation.getStatusElement().getExtension().size()); + } + +} diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 60887c1e899..c9da934ded4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -145,6 +145,11 @@ extensions on fields in the resonse Bundle (e.g. Bundle.entry.search). Thanks to GitHub user am202 for reporting! + + Primitive elements with no value but an extension were sometimes not + encoded correctly in XML, and sometimes not parsed correctly in JSON. + Thanks to Bill de Beaubien for reporting! +