Fix #414: Parser should not choke with a NullPointerException if it

encounters an extension without a URL
This commit is contained in:
James Agnew 2016-09-30 17:35:29 -04:00
parent 22c2d301dc
commit 26cd316343
17 changed files with 383 additions and 74 deletions

View File

@ -130,6 +130,7 @@ public class ExtensionDt extends BaseIdentifiableElement implements ICompositeDa
myModifier = theModifier;
}
@Override
public ExtensionDt setUrl(String theUrl) {
myUrl = theUrl != null ? new StringDt(theUrl) : myUrl;
return this;
@ -140,6 +141,7 @@ public class ExtensionDt extends BaseIdentifiableElement implements ICompositeDa
return this;
}
@Override
public ExtensionDt setValue(IBaseDatatype theValue) {
myValue = theValue;
return this;

View File

@ -50,4 +50,9 @@ public class ErrorHandlerAdapter implements IParserErrorHandler {
// NOP
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
// NOP
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
/*
* #%L
* HAPI FHIR - Core Library
@ -35,13 +37,22 @@ public interface IParserErrorHandler {
*/
void containedResourceWithNoId(IParseLocation theLocation);
/**
* Resource was missing a required element
*
* @param theLocation
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theReference The actual invalid reference (e.g. "#3")
* @since 2.1
*/
void missingRequiredElement(IParseLocation theLocation, String theElementName);
/**
* 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.
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theElementName
* The name of the element that was found.
* @since 1.2
@ -52,8 +63,7 @@ public interface IParserErrorHandler {
* 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.
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theAttributeName
* The name of the attribute that was found.
*/
@ -63,8 +73,7 @@ public interface IParserErrorHandler {
* 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.
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theElementName
* The name of the element that was found.
*/
@ -75,8 +84,7 @@ public interface IParserErrorHandler {
* 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.
* The location in the document. Note that this may be <code>null</code> as the ParseLocation feature is experimental. Use with caution, as the API may change.
* @param theReference The actual invalid reference (e.g. "#3")
* @since 2.0
*/
@ -88,7 +96,13 @@ public interface IParserErrorHandler {
* locations can be added to the API in a future release without changing the API.
*/
public interface IParseLocation {
// nothing for now
/**
* Returns the name of the parent element (the element containing the element currently being parsed)
* @since 2.1
*/
String getParentElementName();
}
}

View File

@ -1299,7 +1299,20 @@ public class JsonParser extends BaseParser implements IParser {
private void parseExtension(ParserState<?> theState, JsonArray theValues, boolean theIsModifier) {
for (int i = 0; i < theValues.size(); i++) {
JsonObject nextExtObj = (JsonObject) theValues.get(i);
String url = nextExtObj.get("url").getAsString();
JsonElement jsonElement = nextExtObj.get("url");
String url;
if (!(jsonElement instanceof JsonPrimitive)) {
String parentElementName;
if (theIsModifier) {
parentElementName = "modifierExtension";
} else {
parentElementName = "extension";
}
getErrorHandler().missingRequiredElement(new ParseLocation(parentElementName), "url");
url = null;
} else {
url = jsonElement.getAsString();
}
theState.enteringNewElementExtension(null, url, theIsModifier);
for (Iterator<Entry<String, JsonElement>> iter = nextExtObj.entrySet().iterator(); iter.hasNext();) {
String next = iter.next().getKey();

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
/*
* #%L
@ -85,4 +86,11 @@ public class LenientErrorHandler implements IParserErrorHandler {
}
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
ourLog.warn("Resource is missing required element: {}", theElementName);
}
}
}

View File

@ -0,0 +1,22 @@
package ca.uhn.fhir.parser;
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
class ParseLocation implements IParseLocation {
private String myParentElementName;
/**
* Constructor
*/
public ParseLocation(String theParentElementName) {
super();
myParentElementName = theParentElementName;
}
@Override
public String getParentElementName() {
return myParentElementName;
}
}

View File

@ -830,7 +830,8 @@ class ParserState<T> {
@SuppressWarnings("unused")
public void enteringNewElementExtension(StartElement theElement, String theUrlAttr, boolean theIsModifier) {
if (myPreResourceState != null && getCurrentElement() instanceof ISupportsUndeclaredExtensions) {
ExtensionDt newExtension = new ExtensionDt(theIsModifier, theUrlAttr);
ExtensionDt newExtension = new ExtensionDt(theIsModifier);
newExtension.setUrl(theUrlAttr);
ISupportsUndeclaredExtensions elem = (ISupportsUndeclaredExtensions) getCurrentElement();
elem.addUndeclaredExtension(newExtension);
ExtensionState newState = new ExtensionState(myPreResourceState, newExtension);

View File

@ -56,5 +56,19 @@ public class StrictErrorHandler implements IParserErrorHandler {
throw new DataFormatException("Resource has invalid reference: " + theReference);
}
@Override
public void missingRequiredElement(IParseLocation theLocation, String theElementName) {
StringBuilder b = new StringBuilder();
b.append("Resource is missing required element '");
b.append(theElementName);
b.append("'");
if (theLocation != null) {
b.append(" in parent element '");
b.append(theLocation.getParentElementName());
b.append("'");
}
throw new DataFormatException(b.toString());
}
}

View File

@ -205,16 +205,24 @@ public class XmlParser extends BaseParser implements IParser {
if ("extension".equals(elem.getName().getLocalPart())) {
Attribute urlAttr = elem.getAttributeByName(new QName("url"));
String url;
if (urlAttr == null || isBlank(urlAttr.getValue())) {
throw new DataFormatException("Extension element has no 'url' attribute");
getErrorHandler().missingRequiredElement(new ParseLocation("extension"), "url");
url = null;
} else {
url = urlAttr.getValue();
}
parserState.enteringNewElementExtension(elem, urlAttr.getValue(), false);
parserState.enteringNewElementExtension(elem, url, false);
} else if ("modifierExtension".equals(elem.getName().getLocalPart())) {
Attribute urlAttr = elem.getAttributeByName(new QName("url"));
String url;
if (urlAttr == null || isBlank(urlAttr.getValue())) {
throw new DataFormatException("Extension element has no 'url' attribute");
getErrorHandler().missingRequiredElement(new ParseLocation("modifierExtension"), "url");
url = null;
} else {
url = urlAttr.getValue();
}
parserState.enteringNewElementExtension(elem, urlAttr.getValue(), true);
parserState.enteringNewElementExtension(elem, url, true);
} else {
String elementName = elem.getName().getLocalPart();
parserState.enteringNewElement(namespaceURI, elementName);

View File

@ -23,6 +23,7 @@ import java.util.UUID;
import org.apache.commons.io.IOUtils;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Test;
@ -106,8 +107,6 @@ public class JsonParserDstu2Test {
assertEquals("ORG", o.getName());
}
/**
* See #308
*/
@ -125,6 +124,7 @@ public class JsonParserDstu2Test {
obs = p.parseResource(ReportObservation.class, encoded);
assertEquals(true, obs.getReadOnly().getValue().booleanValue());
}
/**
* See #390
@ -245,7 +245,7 @@ public class JsonParserDstu2Test {
p = (Patient) ourCtx.newJsonParser().parseResource("{\"resourceType\":\"Patient\",\"language\":[\"en_CA\"]}");
assertEquals("en_CA", p.getLanguage().getValue());
}
@Test
public void testEncodeAndParseMetaProfileAndTags() {
Patient p = new Patient();
@ -302,7 +302,6 @@ public class JsonParserDstu2Test {
assertEquals(new Tag("scheme2", "term2", "label2"), tagList.get(1));
}
/**
* See #336
*/
@ -360,7 +359,7 @@ public class JsonParserDstu2Test {
}
@Test
public void testEncodeAndParseSecurityLabels() {
Patient p = new Patient();
@ -425,7 +424,7 @@ public class JsonParserDstu2Test {
assertEquals("VERSION2", label.getVersion());
}
@Test
public void testEncodeBundleNewBundleNoText() {
@ -446,6 +445,7 @@ public class JsonParserDstu2Test {
assertThat(val, not(containsString("text")));
}
@Test
public void testEncodeBundleOldBundleNoText() {
@ -484,7 +484,7 @@ public class JsonParserDstu2Test {
"}"));
//@formatter:on
}
@Test
public void testEncodeDoesntIncludeUuidId() {
Patient p = new Patient();
@ -535,7 +535,6 @@ public class JsonParserDstu2Test {
assertThat(encoded, not(containsString("Label")));
}
@Test
public void testEncodeExtensionInPrimitiveElement() {
@ -565,7 +564,7 @@ public class JsonParserDstu2Test {
}
@Test
public void testEncodeExtensionUndeclaredNonModifier() {
Observation obs = new Observation();
@ -603,6 +602,7 @@ public class JsonParserDstu2Test {
assertEquals("ext_url_value", ((StringDt)obs.getUndeclaredExtensions().get(0).getValue()).getValue());
}
@Test
public void testEncodeExtensionUndeclaredNonModifierWithChildExtension() {
Observation obs = new Observation();
@ -1362,6 +1362,66 @@ public class JsonParserDstu2Test {
assertEquals("patient family", p.getNameFirstRep().getFamilyAsSingleString());
}
/**
* See #414
*/
@Test
public void testParseJsonExtensionWithoutUrl() {
//@formatter:off
String input =
"{\"resourceType\":\"Patient\"," +
"\"extension\":[ {\"valueDateTime\":\"2011-01-02T11:13:15\"} ]" +
"}";
//@formatter:on
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getAllUndeclaredExtensions().size());
assertEquals(null, parsed.getAllUndeclaredExtensions().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getAllUndeclaredExtensions().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'extension'", e.getMessage());
}
}
/**
* See #414
*/
@Test
public void testParseJsonModifierExtensionWithoutUrl() {
//@formatter:off
String input =
"{\"resourceType\":\"Patient\"," +
"\"modifierExtension\":[ {\"valueDateTime\":\"2011-01-02T11:13:15\"} ]" +
"}";
//@formatter:on
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getAllUndeclaredExtensions().size());
assertEquals(null, parsed.getAllUndeclaredExtensions().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getAllUndeclaredExtensions().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'modifierExtension'", e.getMessage());
}
}
@Test
public void testParseMetadata() throws Exception {
//@formatter:off

View File

@ -68,6 +68,69 @@ public class XmlParserDstu2Test {
}
}
/**
* See #414
*/
@Test
public void testParseXmlExtensionWithoutUrl() {
//@formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
" <extension>\n" +
" <valueDateTime value=\"2011-01-02T11:13:15\"/>\n" +
" </extension>\n" +
"</Patient>";
//@formatter:on
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getAllUndeclaredExtensions().size());
assertEquals(null, parsed.getAllUndeclaredExtensions().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getAllUndeclaredExtensions().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'extension'", e.getCause().getMessage());
}
}
/**
* See #414
*/
@Test
public void testParseXmlModifierExtensionWithoutUrl() {
//@formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
" <modifierExtension>\n" +
" <valueDateTime value=\"2011-01-02T11:13:15\"/>\n" +
" </modifierExtension>\n" +
"</Patient>";
//@formatter:on
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getAllUndeclaredExtensions().size());
assertEquals(null, parsed.getAllUndeclaredExtensions().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getAllUndeclaredExtensions().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'modifierExtension'", e.getCause().getMessage());
}
}
/**
* If a contained resource refers to a contained resource that comes after it, it should still be successfully
* woven together.

View File

@ -3,9 +3,13 @@ package org.hl7.fhir.dstu3.model;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
public abstract class BaseExtension extends Type implements IBaseExtension<Extension, Type>, IBaseHasExtensions {
private static final long serialVersionUID = 1L;
@Override
public Extension setValue(IBaseDatatype theValue) {
setValue((Type)theValue);
@ -14,4 +18,13 @@ public abstract class BaseExtension extends Type implements IBaseExtension<Exten
public abstract Extension setValue(Type theValue);
/**
* Returns the value of this extension cast as a {@link IPrimitiveType}. This method is just a convenience method for easy chaining.
*/
public IPrimitiveType<?> getValueAsPrimitive() {
return (IPrimitiveType<?>)getValue();
}
}

View File

@ -36,6 +36,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -388,6 +389,5 @@ public class Extension extends BaseExtension implements IBaseExtension<Extension
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(url, value);
}
}

View File

@ -31,6 +31,7 @@ public class ErrorHandlerTest {
new ErrorHandlerAdapter().unknownElement(null, null);
new ErrorHandlerAdapter().containedResourceWithNoId(null);
new ErrorHandlerAdapter().unknownReference(null, null);
new ErrorHandlerAdapter().missingRequiredElement(null, null);
}
@Test

View File

@ -56,6 +56,38 @@ public class JsonParserDstu3Test {
public void after() {
ourCtx.setNarrativeGenerator(null);
}
/**
* See #414
*/
@Test
public void testParseJsonModifierExtensionWithoutUrl() {
//@formatter:off
String input =
"{\"resourceType\":\"Patient\"," +
"\"modifierExtension\":[ {\"valueDateTime\":\"2011-01-02T11:13:15\"} ]" +
"}";
//@formatter:on
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getModifierExtension().size());
assertEquals(null, parsed.getModifierExtension().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getModifierExtension().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'modifierExtension'", e.getMessage());
}
}
@Test
public void testParseMissingArray() throws IOException {
@ -82,6 +114,8 @@ public class JsonParserDstu3Test {
assertThat(output, containsString("\"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">VALUE</div>\""));
}
@Test
public void testEncodeNarrativeShouldIncludeNamespaceWithProcessingInstruction() {
@ -137,6 +171,37 @@ public class JsonParserDstu3Test {
assertThat(encode, containsString("\"value\": \"APPID\""));
}
/**
* See #414
*/
@Test
public void testParseJsonExtensionWithoutUrl() {
//@formatter:off
String input =
"{\"resourceType\":\"Patient\"," +
"\"extension\":[ {\"valueDateTime\":\"2011-01-02T11:13:15\"} ]" +
"}";
//@formatter:on
IParser parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getExtension().size());
assertEquals(null, parsed.getExtension().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getExtension().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newJsonParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'extension'", e.getMessage());
}
}
/**
* See #344
*/

View File

@ -23,8 +23,6 @@ import java.io.IOException;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import org.apache.commons.io.IOUtils;
@ -78,6 +76,69 @@ public class XmlParserDstu3Test {
ourCtx.setNarrativeGenerator(null);
}
/**
* See #414
*/
@Test
public void testParseXmlExtensionWithoutUrl() {
//@formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
" <extension>\n" +
" <valueDateTime value=\"2011-01-02T11:13:15\"/>\n" +
" </extension>\n" +
"</Patient>";
//@formatter:on
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getExtension().size());
assertEquals(null, parsed.getExtension().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getExtension().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'extension'", e.getCause().getMessage());
}
}
/**
* See #414
*/
@Test
public void testParseXmlModifierExtensionWithoutUrl() {
//@formatter:off
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
" <modifierExtension>\n" +
" <valueDateTime value=\"2011-01-02T11:13:15\"/>\n" +
" </modifierExtension>\n" +
"</Patient>";
//@formatter:on
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new LenientErrorHandler());
Patient parsed = (Patient) parser.parseResource(input);
assertEquals(1, parsed.getModifierExtension().size());
assertEquals(null, parsed.getModifierExtension().get(0).getUrl());
assertEquals("2011-01-02T11:13:15", parsed.getModifierExtension().get(0).getValueAsPrimitive().getValueAsString());
try {
parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
parser.parseResource(input);
fail();
} catch (DataFormatException e) {
assertEquals("Resource is missing required element 'url' in parent element 'modifierExtension'", e.getCause().getMessage());
}
}
@Test
public void testEncodeChainedContainedResourcer() {
Organization gp = new Organization();
@ -1868,53 +1929,7 @@ public class XmlParserDstu3Test {
assertThat(output, containsString("<text><status value=\"generated\"/><div xmlns=\"http://www.w3.org/1999/xhtml\"><div class=\"hapiHeaderText\">John <b>SMITH </b>"));
}
@Test
public void testExceptionWithoutUrl() {
//@formatter:off
String input =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
"<Patient xmlns=\"http://hl7.org/fhir\">" +
"<extension>" +
"<valueString value=\"FOO\">" +
"</extension>" +
"<address>" +
"<line value=\"FOO\"/>" +
"</address>" +
"</Patient>";
//@formatter:on
try {
ourCtx.newXmlParser().parseResource(Patient.class, input);
fail();
} catch (DataFormatException e) {
assertThat(e.toString(), containsString("Extension element has no 'url' attribute"));
}
}
@Test
public void testModifierExceptionWithoutUrl() {
//@formatter:off
String input =
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
"<Patient xmlns=\"http://hl7.org/fhir\">" +
"<modifierExtension>" +
"<valueString value=\"FOO\">" +
"</modifierExtension>" +
"<address>" +
"<line value=\"FOO\"/>" +
"</address>" +
"</Patient>";
//@formatter:on
try {
ourCtx.newXmlParser().parseResource(Patient.class, input);
fail();
} catch (DataFormatException e) {
assertThat(e.toString(), containsString("Extension element has no 'url' attribute"));
}
}
@Test
public void testMoreExtensions() throws Exception {

View File

@ -128,6 +128,11 @@
<![CDATA[<code>MedicationAdministration.medication[x]</code>]]>.
Thanks to GitHub user @Crudelus for reporting!
</action>
<action type="fix" issue="414">
Handle parsing an extension without a URL more gracefully. In HAPI FHIR 2.0 this caused
a NullPointerException to be thrown. Now it will trigger a warning, or throw a
DataFormatException if the StrictErrorHandler is configured on the parser.
</action>
</release>
<release version="2.0" date="2016-08-30">
<action type="fix">