From acdbdc0be7dedb69ac7496cdda196509987c73d7 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 25 Aug 2016 07:32:37 -0400 Subject: [PATCH 1/2] Fix #426 - Extension with datatype of ID failed to parse --- .../java/ca/uhn/fhir/parser/ParserState.java | 103 +++++++++--------- .../jpa/dao/dstu3/FhirSystemDaoDstu3Test.java | 15 +++ .../uhn/fhir/parser/XmlParserDstu3Test.java | 64 ++++++++++- src/changes/changes.xml | 4 + 4 files changed, 129 insertions(+), 57 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java index 63aee5d8acf6..233b9cf87096 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 @@ -10,7 +10,7 @@ * 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, @@ -105,6 +105,7 @@ class ParserState { private final IParser myParser; private IBase myPreviousElement; private BaseState myState; + private ParserState(IParser theParser, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) { myParser = theParser; myContext = theContext; @@ -223,7 +224,8 @@ public void xmlEvent(XMLEvent theNextEvent) { } } - static ParserState getPreAtomInstance(IParser theParser, FhirContext theContext, Class theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException { + static ParserState getPreAtomInstance(IParser theParser, FhirContext theContext, Class theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) + throws DataFormatException { ParserState retVal = new ParserState(theParser, theContext, theJsonMode, theErrorHandler); if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) { retVal.push(retVal.new PreAtomState(theResourceType)); @@ -237,7 +239,8 @@ static ParserState getPreAtomInstance(IParser theParser, FhirContext the * @param theResourceType * May be null */ - static ParserState getPreResourceInstance(IParser theParser, Class theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException { + static ParserState getPreResourceInstance(IParser theParser, Class theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) + throws DataFormatException { ParserState retVal = new ParserState(theParser, theContext, theJsonMode, theErrorHandler); if (theResourceType == null) { if (theContext.getVersion().getVersion().isRi()) { @@ -1389,7 +1392,7 @@ protected void populateTarget() { @Override public void wereBack() { super.wereBack(); - + IResource res = (IResource) getCurrentElement(); assert res != null; if (res.getId() == null || res.getId().isEmpty()) { @@ -1580,7 +1583,7 @@ public void enteringNewElement(String theNamespace, String theChildName) throws return; } } - + /* * This means we've found an element that doesn't exist on the structure. If the error handler doesn't throw * an exception, swallow the element silently along with any child elements @@ -1740,10 +1743,10 @@ public void attributeValue(String theName, String theValue) throws DataFormatExc } if ("id".equals(theName)) { if (getCurrentElement() instanceof IBaseElement) { - ((IBaseElement)getCurrentElement()).setId(theValue); + ((IBaseElement) getCurrentElement()).setId(theValue); return; } else if (getCurrentElement() instanceof IIdentifiableElement) { - ((IIdentifiableElement)getCurrentElement()).setElementSpecificId(theValue); + ((IIdentifiableElement) getCurrentElement()).setElementSpecificId(theValue); return; } } @@ -1771,42 +1774,35 @@ public void enteringNewElement(String theNamespaceUri, String theLocalPart) thro } BaseRuntimeElementDefinition target = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(theLocalPart); - if (target == null) { - myErrorHandler.unknownElement(null, theLocalPart); - push(new SwallowChildrenWholeState(getPreResourceState())); - return; + + if (target != null) { + switch (target.getChildType()) { + case COMPOSITE_DATATYPE: { + BaseRuntimeElementCompositeDefinition compositeTarget = (BaseRuntimeElementCompositeDefinition) target; + ICompositeType newChildInstance = (ICompositeType) compositeTarget.newInstance(); + myExtension.setValue(newChildInstance); + ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance); + push(newState); + return; + } + case ID_DATATYPE: + case PRIMITIVE_DATATYPE: { + RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; + IPrimitiveType newChildInstance = primitiveTarget.newInstance(); + myExtension.setValue(newChildInstance); + PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance); + push(newState); + return; + } + } } - switch (target.getChildType()) { - case COMPOSITE_DATATYPE: { - BaseRuntimeElementCompositeDefinition compositeTarget = (BaseRuntimeElementCompositeDefinition) target; - ICompositeType newChildInstance = (ICompositeType) compositeTarget.newInstance(); - myExtension.setValue(newChildInstance); - ElementCompositeState newState = new ElementCompositeState(getPreResourceState(), compositeTarget, newChildInstance); - push(newState); - return; - } - case PRIMITIVE_DATATYPE: { - RuntimePrimitiveDatatypeDefinition primitiveTarget = (RuntimePrimitiveDatatypeDefinition) target; - IPrimitiveType newChildInstance = primitiveTarget.newInstance(); - myExtension.setValue(newChildInstance); - PrimitiveState newState = new PrimitiveState(getPreResourceState(), newChildInstance); - push(newState); - return; - } - case PRIMITIVE_XHTML: - case RESOURCE: - case RESOURCE_BLOCK: - case UNDECL_EXT: - case EXTENSION_DECLARED: - case CONTAINED_RESOURCES: - case CONTAINED_RESOURCE_LIST: - case ID_DATATYPE: - case PRIMITIVE_XHTML_HL7ORG: - break; - } + // We hit an invalid type for the extension + myErrorHandler.unknownElement(null, theLocalPart); + push(new SwallowChildrenWholeState(getPreResourceState())); + return; } - + @Override protected IBaseExtension getCurrentElement() { return myExtension; @@ -1989,7 +1985,7 @@ public PreResourceState(PreResourceState thePreResourcesState, FhirVersionEnum t @Override public void endingElement() throws DataFormatException { -// postProcess(); + // postProcess(); stitchBundleCrossReferences(); pop(); } @@ -2090,11 +2086,11 @@ private void postProcess() { } } } - + if (wantedProfileType != null && !wantedProfileType.equals(myInstance.getClass())) { if (myResourceType == null || myResourceType.isAssignableFrom(wantedProfileType)) { - ourLog.debug("Converting resource of type {} to type defined for profile \"{}\": {}", new Object[] {myInstance.getClass().getName(), usedProfile, wantedProfileType}); - + ourLog.debug("Converting resource of type {} to type defined for profile \"{}\": {}", new Object[] { myInstance.getClass().getName(), usedProfile, wantedProfileType }); + /* * This isn't the most efficient thing really.. If we want a specific * type we just re-parse into that type. The problem is that we don't know @@ -2110,7 +2106,7 @@ private void postProcess() { } } } - + FhirTerser terser = myContext.newTerser(); terser.visit(myInstance, new IModelVisitor() { @@ -2146,7 +2142,8 @@ public void acceptElement(IBase theElement, List thePathToElement, BaseR } @Override - public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { + public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainingElement, List thePathToElement, BaseRuntimeChildDefinition theChildDefinition, + BaseRuntimeElementDefinition theDefinition, ExtensionDt theNextExt) { acceptElement(theNextExt.getValue(), null, null, null); } }); @@ -2157,7 +2154,7 @@ public void acceptUndeclaredExtension(ISupportsUndeclaredExtensions theContainin private void stitchBundleCrossReferences() { final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName()); if (bundle) { - + /* * Stitch together resource references */ @@ -2195,7 +2192,7 @@ private void stitchBundleCrossReferences() { } } } - + } } @@ -2224,11 +2221,11 @@ public PreResourceStateHapi(Object theTarget, IMutator theMutator, Class\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + //@formatter:on + Patient pt = ourCtx.newXmlParser().parseResource(Patient.class, input); + + List extList = pt.getExtensionsByUrl("http://aaa.ch/fhir/Patient#mangedcare"); + extList = extList.get(0).getExtensionsByUrl("http://aaa.ch/fhir/Patient#mangedcare-aaa-id"); + Extension ext = extList.get(0); + IdType value = (IdType) ext.getValue(); + assertEquals("mc1", value.getValueAsString()); + } + + /** + * See #426 + * + * Value type of FOO isn't a valid datatype + */ + @Test + public void testParseExtensionWithInvalidType() { + //@formatter:off + String input = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + //@formatter:on + Patient pt = ourCtx.newXmlParser().parseResource(Patient.class, input); + + List extList = pt.getExtensionsByUrl("http://aaa.ch/fhir/Patient#mangedcare"); + extList = extList.get(0).getExtensionsByUrl("http://aaa.ch/fhir/Patient#mangedcare-aaa-id"); + Extension ext = extList.get(0); + IdType value = (IdType) ext.getValue(); + assertEquals(null, value); + } + /** * See #342 */ diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 62cdeffb1b52..5aa90e34a4a0 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -197,6 +197,10 @@ Fix NullPointerException when encoding an extension containing CodeableConcept with log level set to TRACE. Thanks to Bill Denton for the report! + + Parser failed to parse resources containing an extension with a value type of + "id". Thanks to Raphael Mäder for reporting! + From 3c65b64ed7827f90b7a567424d8c5e5a1497c766 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 25 Aug 2016 08:16:27 -0400 Subject: [PATCH 2/2] Fix changelog typos --- .../ca/uhn/fhir/context/FhirVersionEnum.java | 2 +- src/changes/changes.xml | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java index 00deaf72057a..712adc33db2c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirVersionEnum.java @@ -40,7 +40,7 @@ public enum FhirVersionEnum { DSTU2_HL7ORG("org.hl7.fhir.instance.FhirDstu2Hl7Org", DSTU2, true, "1.0.2"), - DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, "1.5.0"); + DSTU3("org.hl7.fhir.dstu3.hapi.ctx.FhirDstu3", null, true, "1.6.0"); private final FhirVersionEnum myEquivalent; private final String myFhirVersionString; diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 5aa90e34a4a0..9969f8786a7e 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -31,7 +31,7 @@ STU3 structure definitions have been updated to the - STU3 ballot candidate versions (1.5.0 - SVN 9395) + STU3 ballot candidate versions (1.6.0 - SVN 9395) Both client and server now support the new Content Types decided in @@ -1452,7 +1452,7 @@ search. This is experimental, since it is not a part of the core FHIR specification. - + Process "Accept: text/xml" and "Accept: text/json" headers was wanting the equivalent FHIR encoding styles. These are not correct, but the intention is clear so we will honour them @@ -1463,13 +1463,13 @@ codes (specifically, ValueSets which defined concepts containing child concepts did not result in Enum values for the child concepts) - + In the JPA server, order of transaction processing should be DELETE, POST, PUT, GET, and the order should not matter within entries with the same verb. Thanks to Bill de Beaubien for reporting! - + Add the ability to wire JPA conformance providers using Spring (basically, add default constructors and setters to the conformance providers). Thanks @@ -1566,11 +1566,11 @@ JPA server now implements the $validate-code operation - + HAPI-FHIR now has support for _summary and _elements parameters, in server, client, and JPA server. - + _revinclude results from JPA server should have a Bundle.entry.search.mode of "include" and not "match". Thanks to Josh Mandel for reporting! @@ -1780,7 +1780,7 @@ Generic/fluent client and JPA server now both support _lastUpdated search parameter which was added in DSTU2 - + JPA server now supports sorting on reference parameters. Thanks to Vishal Kachroo for reporting that this wasn't working! @@ -1864,7 +1864,7 @@ the new syntax required in DSTU2: [resource name]:[search param NAME] insead of the DSTU1 style [resource name].[search param PATH] - + When encoding resources, the parser will now convert any resource references to versionless references automatically (i.e. it will omit the version part automatically if one is present in the reference) @@ -1873,7 +1873,7 @@ reference if the base matches the base for the server giving the response. - + Narrative generator incorrectly sets the Resource.text.status to 'generated' even if the given resource type does not have a template (and therefore no narrative is actually generated). Thanks to Bill de Beaubien for reporting! @@ -1881,7 +1881,7 @@ Searching in JPA server with no search parameter returns deleted resources when it should exclude them. - + Remove Eclipse and IntelliJ artifacts (.project, *.iml, etc) from version control. Thanks to Doug Martin for the suggestion! @@ -2070,7 +2070,7 @@ the method type was a custom resource definition type (instead of a built-in HAPI type). Thanks to Neal Acharya for the analysis. - + JPA server module now supports _include]]> value of