diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ErrorHandlerAdapter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ErrorHandlerAdapter.java index eb46b9bcfd5..a7be972bb11 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ErrorHandlerAdapter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ErrorHandlerAdapter.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.parser; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -24,9 +24,14 @@ package ca.uhn.fhir.parser; * Adapter implementation with NOP implementations of all {@link IParserErrorHandler} methods. */ public class ErrorHandlerAdapter implements IParserErrorHandler { - + @Override - public void unknownElement(IParseLocation theLocation, String theElementName) { + public void containedResourceWithNoId(IParseLocation theLocation) { + // NOP + } + + @Override + public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { // NOP } @@ -36,7 +41,12 @@ public class ErrorHandlerAdapter implements IParserErrorHandler { } @Override - public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) { + public void unknownElement(IParseLocation theLocation, String theElementName) { + // NOP + } + + @Override + public void unknownReference(IParseLocation theLocation, String theReference) { // NOP } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParserErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParserErrorHandler.java index a5c4ff91029..d0daac666bb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParserErrorHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParserErrorHandler.java @@ -10,7 +10,7 @@ package ca.uhn.fhir.parser; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -25,32 +25,63 @@ package ca.uhn.fhir.parser; */ public interface IParserErrorHandler { + /** + * Invoked when a contained resource is parsed that has no ID specified (and is therefore invalid) + * + * @param theLocation + * The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without + * changing the API. + * @since 2.0 + */ + void containedResourceWithNoId(IParseLocation theLocation); + /** * Invoked when an element repetition (e.g. a second repetition of something) is found for a field * which is non-repeating. * - * @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API. - * @param theElementName The name of the element that was found. + * @param theLocation + * The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without + * changing the API. + * @param theElementName + * The name of the element that was found. * @since 1.2 */ void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName); /** - * Invoked when an unknown element is found in the document. + * Invoked when an unknown element is found in the document. * - * @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API. - * @param theAttributeName The name of the attribute that was found. + * @param theLocation + * The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without + * changing the API. + * @param theAttributeName + * The name of the attribute that was found. */ void unknownAttribute(IParseLocation theLocation, String theAttributeName); /** - * Invoked when an unknown element is found in the document. + * Invoked when an unknown element is found in the document. * - * @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API. - * @param theElementName The name of the element that was found. + * @param theLocation + * The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without + * changing the API. + * @param theElementName + * The name of the element that was found. */ void unknownElement(IParseLocation theLocation, String theElementName); + /** + * Resource contained a reference that could not be resolved and needs to be resolvable (e.g. because + * it is a local reference to an unknown contained resource) + * + * @param theLocation + * The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without + * changing the API. + * @param theReference The actual invalid reference (e.g. "#3") + * @since 2.0 + */ + void unknownReference(IParseLocation theLocation, String theReference); + /** * For now this is an empty interface. Error handling methods include a parameter of this * type which will currently always be set to null. This interface is included here so that @@ -59,5 +90,5 @@ public interface IParserErrorHandler { public interface IParseLocation { // nothing for now } - + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java index 762ba2d13d8..17adae69150 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java @@ -71,4 +71,18 @@ public class LenientErrorHandler implements IParserErrorHandler { } } + @Override + public void containedResourceWithNoId(IParseLocation theLocation) { + if (myLogErrors) { + ourLog.warn("Resource has contained child resource with no ID"); + } + } + + @Override + public void unknownReference(IParseLocation theLocation, String theReference) { + if (myLogErrors) { + ourLog.warn("Resource has invalid reference: {}", theReference); + } + } + } 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 233b9cf8709..37bcc2a2183 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 @@ -1396,7 +1396,9 @@ class ParserState { IResource res = (IResource) getCurrentElement(); assert res != null; if (res.getId() == null || res.getId().isEmpty()) { - ourLog.debug("Discarding contained resource with no ID!"); + // If there is no ID, we don't keep the resource because it's useless (contained resources + // need an ID to be referred to) + myErrorHandler.containedResourceWithNoId(null); } else { if (!res.getId().isLocal()) { res.setId(new IdDt('#' + res.getId().getIdPart())); @@ -1436,11 +1438,11 @@ class ParserState { IBaseResource res = getCurrentElement(); assert res != null; if (res.getIdElement() == null || res.getIdElement().isEmpty()) { - ourLog.debug("Discarding contained resource with no ID!"); + // If there is no ID, we don't keep the resource because it's useless (contained resources + // need an ID to be referred to) + myErrorHandler.containedResourceWithNoId(null); } else { - if (!res.getIdElement().isLocal()) { - res.getIdElement().setValue('#' + res.getIdElement().getIdPart()); - } + res.getIdElement().setValue('#' + res.getIdElement().getIdPart()); getPreResourceState().getContainedResources().put(res.getIdElement().getValue(), res); } @@ -1985,11 +1987,54 @@ class ParserState { @Override public void endingElement() throws DataFormatException { - // postProcess(); stitchBundleCrossReferences(); pop(); } + protected void weaveContainedResources() { + FhirTerser terser = myContext.newTerser(); + terser.visit(myInstance, new IModelVisitor() { + + @Override + public void acceptElement(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { + if (theElement instanceof BaseResourceReferenceDt) { + BaseResourceReferenceDt nextRef = (BaseResourceReferenceDt) theElement; + String ref = nextRef.getReference().getValue(); + if (isNotBlank(ref)) { + if (ref.startsWith("#")) { + IResource target = (IResource) myContainedResources.get(ref); + if (target != null) { + ourLog.debug("Resource contains local ref {} in field {}", ref, thePathToElement); + nextRef.setResource(target); + } else { + myErrorHandler.unknownReference(null, ref); + } + } + } + } else if (theElement instanceof IBaseReference) { + IBaseReference nextRef = (IBaseReference) theElement; + String ref = nextRef.getReferenceElement().getValue(); + if (isNotBlank(ref)) { + if (ref.startsWith("#")) { + IBaseResource target = myContainedResources.get(ref); + if (target != null) { + ourLog.debug("Resource contains local ref {} in field {}", ref, thePathToElement); + nextRef.setResource(target); + } else { + myErrorHandler.unknownReference(null, ref); + } + } + } + } + } + + @Override + public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { + acceptElement(theNextExt.getValue(), null, null, null); + } + }); + } + @Override public void enteringNewElement(String theNamespaceUri, String theLocalPart) throws DataFormatException { BaseRuntimeElementDefinition definition; @@ -2106,48 +2151,7 @@ class ParserState { } } } - - FhirTerser terser = myContext.newTerser(); - terser.visit(myInstance, new IModelVisitor() { - - @Override - public void acceptElement(IBase theElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition) { - if (theElement instanceof BaseResourceReferenceDt) { - BaseResourceReferenceDt nextRef = (BaseResourceReferenceDt) theElement; - String ref = nextRef.getReference().getValue(); - if (isNotBlank(ref)) { - if (ref.startsWith("#")) { - IResource target = (IResource) myContainedResources.get(ref); - if (target != null) { - nextRef.setResource(target); - } else { - ourLog.warn("Resource contains unknown local ref: " + ref); - } - } - } - } else if (theElement instanceof IBaseReference) { - IBaseReference nextRef = (IBaseReference) theElement; - String ref = nextRef.getReferenceElement().getValue(); - if (isNotBlank(ref)) { - if (ref.startsWith("#")) { - IBaseResource target = myContainedResources.get(ref); - if (target != null) { - nextRef.setResource(target); - } else { - ourLog.warn("Resource contains unknown local ref: " + ref); - } - } - } - } - } - - @Override - public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, - BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { - acceptElement(theNextExt.getValue(), null, null, null); - } - }); - + populateTarget(); } @@ -2229,6 +2233,7 @@ class ParserState { @Override protected void populateTarget() { + weaveContainedResources(); if (myEntry != null) { myEntry.setResource((IResource) getCurrentElement()); } @@ -2274,6 +2279,7 @@ class ParserState { @Override protected void populateTarget() { + weaveContainedResources(); if (myMutator != null) { myMutator.setValue(myTarget, getCurrentElement()); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java index a838b70a8cd..c5a24776ad2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java @@ -46,5 +46,15 @@ public class StrictErrorHandler implements IParserErrorHandler { throw new DataFormatException("Multiple repetitions of non-repeatable element '" + theElementName + "' found during parse"); } + @Override + public void containedResourceWithNoId(IParseLocation theLocation) { + throw new DataFormatException("Resource has contained child resource with no ID"); + } + + @Override + public void unknownReference(IParseLocation theLocation, String theReference) { + throw new DataFormatException("Resource has invalid reference: " + theReference); + } + } diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java index aedece5f3c2..30f63f80874 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/parser/XmlParserDstu2Test.java @@ -20,6 +20,7 @@ import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.util.*; import org.apache.commons.io.IOUtils; @@ -67,6 +68,64 @@ public class XmlParserDstu2Test { } } + /** + * If a contained resource refers to a contained resource that comes after it, it should still be successfully + * woven together. + */ + @Test + public void testParseWovenContainedResources() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_woven_obs.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + ca.uhn.fhir.model.dstu2.resource.Bundle bundle = parser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, string); + + DiagnosticReport resource = (DiagnosticReport) bundle.getEntry().get(0).getResource(); + Observation obs = (Observation) resource.getResult().get(1).getResource(); + assertEquals("#2", obs.getId().getValue()); + ResourceReferenceDt performerFirstRep = obs.getPerformer().get(0); + Practitioner performer = (Practitioner) performerFirstRep.getResource(); + assertEquals("#3", performer.getId().getValue()); + } + + @Test(expected=DataFormatException.class) + public void testContainedResourceWithNoId() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + parser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, string); + } + + + @Test() + public void testContainedResourceWithNoIdLenient() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new LenientErrorHandler()); + parser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, string); + } + + @Test(expected=DataFormatException.class) + public void testParseWithInvalidLocalRef() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + parser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, string); + } + + @Test() + public void testParseWithInvalidLocalRefLenient() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new LenientErrorHandler()); + parser.parseResource(ca.uhn.fhir.model.dstu2.resource.Bundle.class, string); + } + + @Test public void testBundleWithBinary() { //@formatter:off diff --git a/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_contained_with_no_id.xml b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_contained_with_no_id.xml new file mode 100644 index 00000000000..cd189a0838e --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_contained_with_no_id.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_invalid_contained_ref.xml b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_invalid_contained_ref.xml new file mode 100644 index 00000000000..d10ece0f0b4 --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_invalid_contained_ref.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_woven_obs.xml b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_woven_obs.xml new file mode 100644 index 00000000000..aa9a89d693e --- /dev/null +++ b/hapi-fhir-structures-dstu2/src/test/resources/bundle_with_woven_obs.xml @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerAdapterTest.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerAdapterTest.java deleted file mode 100644 index 61140664466..00000000000 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerAdapterTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package ca.uhn.fhir.parser; - -import org.junit.Test; - -import ca.uhn.fhir.parser.ErrorHandlerAdapter; -import ca.uhn.fhir.parser.IParserErrorHandler; - -/* - * #%L - * HAPI FHIR - Core Library - * %% - * Copyright (C) 2014 - 2016 University Health Network - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ - -/** - * Adapter implementation with NOP implementations of all {@link IParserErrorHandler} methods. - */ -public class ErrorHandlerAdapterTest { - - @Test - public void testMethods() { - new ErrorHandlerAdapter().unexpectedRepeatingElement(null, null); - new ErrorHandlerAdapter().unknownAttribute(null, null); - new ErrorHandlerAdapter().unknownElement(null, null); - } -} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java new file mode 100644 index 00000000000..fc7d83c77c5 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/parser/ErrorHandlerTest.java @@ -0,0 +1,70 @@ +package ca.uhn.fhir.parser; + +import org.junit.Test; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2016 University Health Network + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +public class ErrorHandlerTest { + + @Test + public void testAdapterMethods() { + new ErrorHandlerAdapter().unexpectedRepeatingElement(null, null); + new ErrorHandlerAdapter().unknownAttribute(null, null); + new ErrorHandlerAdapter().unknownElement(null, null); + new ErrorHandlerAdapter().containedResourceWithNoId(null); + new ErrorHandlerAdapter().unknownReference(null, null); + } + + @Test + public void testLenientMethods() { + new LenientErrorHandler().unexpectedRepeatingElement(null, null); + new LenientErrorHandler().unknownAttribute(null, null); + new LenientErrorHandler().unknownElement(null, null); + new LenientErrorHandler().containedResourceWithNoId(null); + new LenientErrorHandler().unknownReference(null, null); + } + + @Test(expected = DataFormatException.class) + public void testStrictMethods1() { + new StrictErrorHandler().unexpectedRepeatingElement(null, null); + } + + @Test(expected = DataFormatException.class) + public void testStrictMethods2() { + new StrictErrorHandler().unknownAttribute(null, null); + } + + @Test(expected = DataFormatException.class) + public void testStrictMethods3() { + new StrictErrorHandler().unknownElement(null, null); + } + + @Test(expected = DataFormatException.class) + public void testStrictMethods4() { + new StrictErrorHandler().containedResourceWithNoId(null); + } + + @Test(expected = DataFormatException.class) + public void testStrictMethods5() { + new StrictErrorHandler().unknownReference(null, null); + } + +} 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 77adf7b90e4..0b2de9ab871 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 @@ -21,13 +21,9 @@ import static org.mockito.Mockito.verify; import java.io.IOException; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashSet; -import java.util.List; -import java.util.UUID; +import java.util.*; import org.apache.commons.io.IOUtils; import org.custommonkey.xmlunit.Diff; @@ -44,6 +40,7 @@ import org.hl7.fhir.dstu3.model.Bundle.BundleType; import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem; import org.hl7.fhir.dstu3.model.DiagnosticReport.DiagnosticReportStatus; import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent; +import org.hl7.fhir.dstu3.model.Enumeration; import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender; import org.hl7.fhir.dstu3.model.Enumerations.DocumentReferenceStatus; import org.hl7.fhir.dstu3.model.HumanName.NameUse; @@ -51,11 +48,7 @@ import org.hl7.fhir.dstu3.model.Identifier.IdentifierUse; import org.hl7.fhir.dstu3.model.Observation.ObservationRelationshipType; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; +import org.junit.*; import org.mockito.ArgumentCaptor; import com.google.common.collect.Sets; @@ -83,6 +76,63 @@ public class XmlParserDstu3Test { ourCtx.setNarrativeGenerator(null); } + /** + * If a contained resource refers to a contained resource that comes after it, it should still be successfully + * woven together. + */ + @Test + public void testParseWovenContainedResources() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_woven_obs.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + org.hl7.fhir.dstu3.model.Bundle bundle = parser.parseResource(Bundle.class, string); + + DiagnosticReport resource = (DiagnosticReport) bundle.getEntry().get(0).getResource(); + Observation obs = (Observation) resource.getResult().get(1).getResource(); + assertEquals("#2", obs.getId()); + Reference performerFirstRep = obs.getPerformerFirstRep(); + Practitioner performer = (Practitioner) performerFirstRep.getResource(); + assertEquals("#3", performer.getId()); + } + + @Test(expected=DataFormatException.class) + public void testContainedResourceWithNoId() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + parser.parseResource(Bundle.class, string); + } + + + @Test() + public void testContainedResourceWithNoIdLenient() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_contained_with_no_id.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new LenientErrorHandler()); + parser.parseResource(Bundle.class, string); + } + + @Test(expected=DataFormatException.class) + public void testParseWithInvalidLocalRef() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new StrictErrorHandler()); + parser.parseResource(Bundle.class, string); + } + + @Test() + public void testParseWithInvalidLocalRefLenient() throws IOException { + String string = IOUtils.toString(getClass().getResourceAsStream("/bundle_with_invalid_contained_ref.xml"), StandardCharsets.UTF_8); + + IParser parser = ourCtx.newXmlParser(); + parser.setParserErrorHandler(new LenientErrorHandler()); + parser.parseResource(Bundle.class, string); + } + @Test public void testBundleWithBinary() { //@formatter:off diff --git a/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_contained_with_no_id.xml b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_contained_with_no_id.xml new file mode 100644 index 00000000000..cd189a0838e --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_contained_with_no_id.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_invalid_contained_ref.xml b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_invalid_contained_ref.xml new file mode 100644 index 00000000000..d10ece0f0b4 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_invalid_contained_ref.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_woven_obs.xml b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_woven_obs.xml new file mode 100644 index 00000000000..aa9a89d693e --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/test/resources/bundle_with_woven_obs.xml @@ -0,0 +1,317 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 2e1bbbb07b0..c38cdb2e9ed 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -197,6 +197,15 @@ Fix NullPointerException when encoding an extension containing CodeableConcept with log level set to TRACE. Thanks to Bill Denton for the report! + + Add two new methods to the parser error handler that let users trap + invalid contained resources with no ID, as well as references to contained + resource that do not exist. + + + Improve performance when parsing resources containing contained resources + by eliminating a step where references were woven twice + Parser failed to parse resources containing an extension with a value type of "id". Thanks to Raphael Mäder for reporting!