diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java
index 2a3480e3c67..69c13f0f0e7 100644
--- a/examples/src/main/java/example/GenericClientExample.java
+++ b/examples/src/main/java/example/GenericClientExample.java
@@ -205,7 +205,7 @@ public class GenericClientExample {
.forResource(Patient.class)
.where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01"))
.and(Patient.CAREPROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health")))
- .returnBundle(Bundle.class)
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute();
// END SNIPPET: search
diff --git a/examples/src/main/java/example/ValidatorExamples.java b/examples/src/main/java/example/ValidatorExamples.java
index 060d3d5e5f4..1a901ccd553 100644
--- a/examples/src/main/java/example/ValidatorExamples.java
+++ b/examples/src/main/java/example/ValidatorExamples.java
@@ -10,11 +10,30 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
+import ca.uhn.fhir.parser.StrictErrorHandler;
+import ca.uhn.fhir.rest.client.IGenericClient;
+import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
public class ValidatorExamples {
+ public void enableValidation() {
+ // START SNIPPET: enableValidation
+ FhirContext ctx = FhirContext.forDstu2();
+
+ ctx.setParserErrorHandler(new StrictErrorHandler());
+
+ // This client will have strict parser validation enabled
+ IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2");
+
+ // This server will have strict parser validation enabled
+ RestfulServer server = new RestfulServer();
+ server.setFhirContext(ctx);
+
+ // END SNIPPET: enableValidation
+ }
+
public void validateResource() {
// START SNIPPET: basicValidation
// As always, you need a context
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index f4f6493aa83..eecff3f641a 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -40,8 +40,10 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.view.ViewGenerator;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.parser.IParserErrorHandler;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.JsonParser;
+import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.parser.XmlParser;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
@@ -81,6 +83,7 @@ public class FhirContext {
private volatile Map myNameToResourceDefinition = Collections.emptyMap();
private volatile Map> myNameToResourceType;
private volatile INarrativeGenerator myNarrativeGenerator;
+ private volatile IParserErrorHandler myParserErrorHandler = new LenientErrorHandler();
private volatile IRestfulClientFactory myRestfulClientFactory;
private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private final IFhirVersion myVersion;
@@ -136,14 +139,6 @@ public class FhirContext {
return getLocalizer().getMessage(FhirContext.class, "unknownResourceName", theResourceName, theVersion);
}
- /**
- * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
- * for extending the core library.
- */
- public BaseRuntimeElementDefinition> getElementDefinition(String theElementName) {
- return myNameToElementDefinition.get(theElementName);
- }
-
/**
* Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
* for extending the core library.
@@ -157,11 +152,26 @@ public class FhirContext {
return retVal;
}
+ /**
+ * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed
+ * for extending the core library.
+ */
+ public BaseRuntimeElementDefinition> getElementDefinition(String theElementName) {
+ return myNameToElementDefinition.get(theElementName);
+ }
+
/** For unit tests only */
int getElementDefinitionCount() {
return myClassToElementDefinition.size();
}
+ /**
+ * Returns all element definitions (resources, datatypes, etc.)
+ */
+ public Collection> getElementDefinitions() {
+ return Collections.unmodifiableCollection(myClassToElementDefinition.values());
+ }
+
/**
* This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with
* caution
@@ -316,7 +326,7 @@ public class FhirContext {
*
*/
public IParser newJsonParser() {
- return new JsonParser(this);
+ return new JsonParser(this, myParserErrorHandler);
}
/**
@@ -393,7 +403,7 @@ public class FhirContext {
*
*/
public IParser newXmlParser() {
- return new XmlParser(this);
+ return new XmlParser(this, myParserErrorHandler);
}
private BaseRuntimeElementDefinition> scanDatatype(Class extends IElement> theResourceType) {
@@ -457,6 +467,16 @@ public class FhirContext {
myNarrativeGenerator = theNarrativeGenerator;
}
+ /**
+ * Sets a parser error handler to use by default on all parsers
+ *
+ * @param theParserErrorHandler The error handler
+ */
+ public void setParserErrorHandler(IParserErrorHandler theParserErrorHandler) {
+ Validate.notNull(theParserErrorHandler, "theParserErrorHandler must not be null");
+ myParserErrorHandler = theParserErrorHandler;
+ }
+
@SuppressWarnings("unchecked")
private List> toElementList(Collection> theResourceTypes) {
if (theResourceTypes == null) {
@@ -515,11 +535,4 @@ public class FhirContext {
return retVal;
}
- /**
- * Returns all element definitions (resources, datatypes, etc.)
- */
- public Collection> getElementDefinitions() {
- return Collections.unmodifiableCollection(myClassToElementDefinition.values());
- }
-
}
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 04acc4efe2a..43697b77e55 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
@@ -37,6 +37,7 @@ import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
@@ -65,13 +66,19 @@ public abstract class BaseParser implements IParser {
private ContainedResources myContainedResources;
private FhirContext myContext;
+ private IParserErrorHandler myErrorHandler;
private boolean myOmitResourceId;
private String myServerBaseUrl;
private boolean myStripVersionsFromReferences = true;
private boolean mySuppressNarratives;
- public BaseParser(FhirContext theContext) {
+ /**
+ * Constructor
+ * @param theParserErrorHandler
+ */
+ public BaseParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
myContext = theContext;
+ myErrorHandler = theParserErrorHandler;
}
private void containResourcesForEncoding(ContainedResources theContained, IBaseResource theResource, IBaseResource theTarget) {
@@ -248,6 +255,10 @@ public abstract class BaseParser implements IParser {
return myContainedResources;
}
+ protected IParserErrorHandler getErrorHandler() {
+ return myErrorHandler;
+ }
+
/**
* If set to true
(default is false
), narratives will not be included in the encoded values.
*/
@@ -260,6 +271,7 @@ public abstract class BaseParser implements IParser {
&& theIncludedResource == false;
}
+ @Override
public boolean isOmitResourceId() {
return myOmitResourceId;
}
@@ -280,6 +292,7 @@ public abstract class BaseParser implements IParser {
return parseBundle(reader);
}
+ @Override
public T parseResource(Class theResourceType, Reader theReader) throws DataFormatException {
T retVal = doParseResource(theResourceType, theReader);
@@ -288,7 +301,7 @@ public abstract class BaseParser implements IParser {
List base = def.getChildByName("base").getAccessor().getValues(retVal);
if (base != null && base.size() > 0) {
IPrimitiveType> baseType = (IPrimitiveType>) base.get(0);
- IBaseResource res = ((IBaseResource) retVal);
+ IBaseResource res = (retVal);
res.setId(new IdDt(baseType.getValueAsString(), def.getName(), res.getIdElement().getIdPart(), res.getIdElement().getVersionIdPart()));
}
@@ -350,6 +363,14 @@ public abstract class BaseParser implements IParser {
return parseTagList(new StringReader(theString));
}
+ @Override
+ public BaseParser setParserErrorHandler(IParserErrorHandler theErrorHandler) {
+ Validate.notNull(theErrorHandler, "theErrorHandler must not be null");
+ myErrorHandler = theErrorHandler;
+ return this;
+ }
+
+ @Override
public BaseParser setOmitResourceId(boolean theOmitResourceId) {
myOmitResourceId = theOmitResourceId;
return this;
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
new file mode 100644
index 00000000000..e77119a69f9
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ErrorHandlerAdapter.java
@@ -0,0 +1,18 @@
+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) {
+ // NOP
+ }
+
+ @Override
+ public void unknownAttribute(IParseLocation theLocation, String theElementName) {
+ // NOP
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
index 94ebeccf361..1ff913a68dd 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParser.java
@@ -39,6 +39,12 @@ import ca.uhn.fhir.model.api.TagList;
*/
public interface IParser {
+ /**
+ * Registers an error handler which will be invoked when any parse errors are found
+ * @param theErrorHandler The error handler to set. Must not be null.
+ */
+ IParser setParserErrorHandler(IParserErrorHandler theErrorHandler);
+
String encodeBundleToString(Bundle theBundle) throws DataFormatException;
void encodeBundleToWriter(Bundle theBundle, Writer theWriter) throws IOException, DataFormatException;
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
new file mode 100644
index 00000000000..7b53d450ded
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/IParserErrorHandler.java
@@ -0,0 +1,33 @@
+package ca.uhn.fhir.parser;
+
+/**
+ * Error handler
+ */
+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.
+ * @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.
+ *
+ * @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);
+
+ /**
+ * 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
+ * locations can be added to the API in a future release without changing the API.
+ */
+ public interface IParseLocation {
+ // nothing for now
+ }
+
+}
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 51d79672356..d2b2b83ed1f 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
@@ -137,9 +137,10 @@ public class JsonParser extends BaseParser implements IParser {
/**
* Do not use this constructor, the recommended way to obtain a new instance of the JSON parser is to invoke
* {@link FhirContext#newJsonParser()}.
+ * @param theParserErrorHandler
*/
- public JsonParser(FhirContext theContext) {
- super(theContext);
+ public JsonParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
+ super(theContext, theParserErrorHandler);
myContext = theContext;
}
@@ -965,7 +966,7 @@ public class JsonParser extends BaseParser implements IParser {
throw new DataFormatException("Trying to parse bundle but found resourceType other than 'Bundle'. Found: '" + resourceType + "'");
}
- ParserState state = ParserState.getPreAtomInstance(myContext, theResourceType, true);
+ ParserState state = ParserState.getPreAtomInstance(myContext, theResourceType, true, getErrorHandler());
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1)) {
state.enteringNewElement(null, "Bundle");
} else {
@@ -1227,7 +1228,7 @@ public class JsonParser extends BaseParser implements IParser {
def = myContext.getResourceDefinition(resourceType);
}
- ParserState extends IBaseResource> state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true);
+ ParserState extends IBaseResource> state = ParserState.getPreResourceInstance(def.getImplementingClass(), myContext, true, getErrorHandler());
state.enteringNewElement(null, def.getName());
parseChildren(object, state);
@@ -1252,7 +1253,7 @@ public class JsonParser extends BaseParser implements IParser {
assertObjectOfType(resourceTypeObj, JsonValue.ValueType.STRING, "resourceType");
String resourceType = ((JsonString) resourceTypeObj).getString();
- ParserState state = ParserState.getPreTagListInstance(myContext, true);
+ ParserState state = ParserState.getPreTagListInstance(myContext, true, getErrorHandler());
state.enteringNewElement(null, resourceType);
parseChildren(object, state);
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
new file mode 100644
index 00000000000..cd842ec2482
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/LenientErrorHandler.java
@@ -0,0 +1,22 @@
+package ca.uhn.fhir.parser;
+
+/**
+ * The default error handler, which logs issues but does not abort parsing
+ *
+ * @see IParser#setParserErrorHandler(IParserErrorHandler)
+ */
+public class LenientErrorHandler implements IParserErrorHandler {
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
+
+ @Override
+ public void unknownElement(IParseLocation theLocation, String theElementName) {
+ ourLog.warn("Unknown element '{}' found while parsing", theElementName);
+ }
+
+ @Override
+ public void unknownAttribute(IParseLocation theLocation, String theElementName) {
+ ourLog.warn("Unknown attribute '{}' found while parsing", theElementName);
+ }
+
+}
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 0d76e8dbfcd..57d19cbea43 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
@@ -38,11 +38,11 @@ import org.hl7.fhir.instance.model.api.IBaseElement;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
+import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IDomainResource;
-import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
@@ -62,7 +62,6 @@ import ca.uhn.fhir.model.api.BaseBundle;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.ExtensionDt;
-import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IIdentifiableElement;
@@ -90,10 +89,12 @@ class ParserState {
private boolean myJsonMode;
private T myObject;
private BaseState myState;
+ private IParserErrorHandler myErrorHandler;
- private ParserState(FhirContext theContext, boolean theJsonMode) {
+ private ParserState(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
myContext = theContext;
myJsonMode = theJsonMode;
+ myErrorHandler = theErrorHandler;
}
public void attributeValue(String theName, String theValue) throws DataFormatException {
@@ -212,8 +213,8 @@ class ParserState {
myState.xmlEvent(theNextEvent);
}
- public static ParserState getPreAtomInstance(FhirContext theContext, Class extends IBaseResource> theResourceType, boolean theJsonMode) throws DataFormatException {
- ParserState retVal = new ParserState(theContext, theJsonMode);
+ public static ParserState getPreAtomInstance(FhirContext theContext, Class extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
+ ParserState retVal = new ParserState(theContext, theJsonMode, theErrorHandler);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
retVal.push(retVal.new PreAtomState(theResourceType));
} else {
@@ -226,8 +227,8 @@ class ParserState {
* @param theResourceType
* May be null
*/
- public static ParserState getPreResourceInstance(Class theResourceType, FhirContext theContext, boolean theJsonMode) throws DataFormatException {
- ParserState retVal = new ParserState(theContext, theJsonMode);
+ public static ParserState getPreResourceInstance(Class theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
+ ParserState retVal = new ParserState(theContext, theJsonMode, theErrorHandler);
if (theResourceType == null) {
if (theContext.getVersion().getVersion() != FhirVersionEnum.DSTU2_HL7ORG) {
retVal.push(retVal.new PreResourceStateHapi(theResourceType));
@@ -244,8 +245,8 @@ class ParserState {
return retVal;
}
- public static ParserState getPreTagListInstance(FhirContext theContext, boolean theJsonMode) {
- ParserState retVal = new ParserState(theContext, theJsonMode);
+ public static ParserState getPreTagListInstance(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
+ ParserState retVal = new ParserState(theContext, theJsonMode, theErrorHandler);
retVal.push(retVal.new PreTagListState());
return retVal;
}
@@ -795,7 +796,7 @@ class ParserState {
@SuppressWarnings("unused")
public void attributeValue(String theName, String theValue) throws DataFormatException {
- // ignore by default
+ myErrorHandler.unknownAttribute(null, theName);
}
public void endingElement() throws DataFormatException {
@@ -804,7 +805,7 @@ class ParserState {
@SuppressWarnings("unused")
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
- // ignore by default
+ myErrorHandler.unknownElement(null, theLocalPart);
}
/**
@@ -1422,7 +1423,9 @@ class ParserState {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition> target = myDefinition.getChildByName(theLocalPart);
if (target == null) {
- throw new DataFormatException("Unknown extension element name: " + theLocalPart);
+ myErrorHandler.unknownElement(null, theLocalPart);
+ push(new SwallowChildrenWholeState(getPreResourceState()));
+ return;
}
switch (target.getChildType()) {
@@ -1494,11 +1497,6 @@ class ParserState {
private class ElementCompositeState extends BaseState {
private BaseRuntimeElementCompositeDefinition> myDefinition;
-
- public BaseRuntimeElementCompositeDefinition> getDefinition() {
- return myDefinition;
- }
-
private IBase myInstance;
public ElementCompositeState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition> theDef, IBase theInstance) {
@@ -1537,15 +1535,17 @@ class ParserState {
try {
child = myDefinition.getChildByNameOrThrowDataFormatException(theChildName);
} catch (DataFormatException e) {
- if (false) {// TODO: make this configurable
- throw e;
- }
- ourLog.warn(e.getMessage());
+ /* 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
+ */
+ myErrorHandler.unknownElement(null, theChildName);
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
BaseRuntimeElementDefinition> target = child.getChildByName(theChildName);
if (target == null) {
+ // This is a bug with the structures and shouldn't happen..
throw new DataFormatException("Found unexpected element '" + theChildName + "' in parent element '" + myDefinition.getName() + "'. Valid names are: " + child.getValidChildNames());
}
@@ -1676,7 +1676,9 @@ class ParserState {
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
BaseRuntimeElementDefinition> target = myContext.getRuntimeChildUndeclaredExtensionDefinition().getChildByName(theLocalPart);
if (target == null) {
- throw new DataFormatException("Unknown extension element name: " + theLocalPart);
+ myErrorHandler.unknownElement(null, theLocalPart);
+ push(new SwallowChildrenWholeState(getPreResourceState()));
+ return;
}
switch (target.getChildType()) {
@@ -1793,7 +1795,9 @@ class ParserState {
}
push(new TagState(tagList));
} else {
- throw new DataFormatException("Unexpected element '" + theLocalPart + "' found in 'meta' element");
+ myErrorHandler.unknownElement(null, theLocalPart);
+ push(new SwallowChildrenWholeState(getPreResourceState()));
+ return;
}
}
@@ -1820,7 +1824,9 @@ class ParserState {
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
- throw new DataFormatException("Unexpected child element '" + theLocalPart + "' in element 'meta'");
+ myErrorHandler.unknownElement(null, theLocalPart);
+ push(new SwallowChildrenWholeState(getPreResourceState()));
+ return;
}
}
@@ -2193,7 +2199,11 @@ class ParserState {
((IBaseElement) myInstance).setId(theValue);
} else if (myInstance instanceof IBaseResource) {
new IdDt(theValue).applyTo((org.hl7.fhir.instance.model.api.IBaseResource) myInstance);
+ } else {
+ myErrorHandler.unknownAttribute(null, theName);
}
+ } else {
+ myErrorHandler.unknownAttribute(null, theName);
}
}
@@ -2215,10 +2225,7 @@ class ParserState {
@Override
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
- if (false) {// TODO: make this configurable
- throw new Error("Element " + theLocalPart + " in primitive!"); // TODO:
- }
- ourLog.warn("Ignoring element {} in primitive tag", theLocalPart);
+ myErrorHandler.unknownElement(null, theLocalPart);
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
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
new file mode 100644
index 00000000000..1522b13cdad
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/StrictErrorHandler.java
@@ -0,0 +1,21 @@
+package ca.uhn.fhir.parser;
+
+/**
+ * Parser error handler which throws a {@link DataFormatException} any time an
+ * issue is found while parsing.
+ *
+ * @see IParser#setParserErrorHandler(IParserErrorHandler)
+ */
+public class StrictErrorHandler implements IParserErrorHandler {
+
+ @Override
+ public void unknownElement(IParseLocation theLocation, String theElementName) {
+ throw new DataFormatException("Unknown element '" + theElementName + "' found during parse");
+ }
+
+ @Override
+ public void unknownAttribute(IParseLocation theLocation, String theAttributeName) {
+ throw new DataFormatException("Unknown attribute '" + theAttributeName + "' found during parse");
+ }
+
+}
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 209b7170a6a..f990bf5836e 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
@@ -20,9 +20,7 @@ package ca.uhn.fhir.parser;
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.defaultString;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.Reader;
@@ -47,25 +45,22 @@ import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.instance.model.api.IBase;
-import org.hl7.fhir.instance.model.api.IBaseResource;
-import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.instance.model.api.IAnyResource;
+import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBinary;
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.IBaseHasModifierExtensions;
-import org.hl7.fhir.instance.model.api.IBaseXhtml;
-import org.hl7.fhir.instance.model.api.IDomainResource;
-import org.hl7.fhir.instance.model.api.IIdType;
-import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IBaseReference;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IBaseXhtml;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
-import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@@ -82,9 +77,7 @@ import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
-import ca.uhn.fhir.model.base.composite.BaseContainedDt;
import ca.uhn.fhir.model.base.composite.BaseNarrativeDt;
-import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
@@ -119,9 +112,10 @@ public class XmlParser extends BaseParser implements IParser {
/**
* 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
*/
- public XmlParser(FhirContext theContext) {
- super(theContext);
+ public XmlParser(FhirContext theContext, IParserErrorHandler theParserErrorHandler) {
+ super(theContext, theParserErrorHandler);
myContext = theContext;
}
@@ -1034,7 +1028,7 @@ public class XmlParser extends BaseParser implements IParser {
}
private Bundle parseBundle(XMLEventReader theStreamReader, Class extends IBaseResource> theResourceType) {
- ParserState parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false);
+ ParserState parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState);
}
@@ -1045,7 +1039,7 @@ public class XmlParser extends BaseParser implements IParser {
}
private T parseResource(Class theResourceType, XMLEventReader theStreamReader) {
- ParserState parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false);
+ ParserState parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState);
}
@@ -1053,7 +1047,7 @@ public class XmlParser extends BaseParser implements IParser {
public TagList parseTagList(Reader theReader) {
XMLEventReader streamReader = createStreamReader(theReader);
- ParserState parserState = ParserState.getPreTagListInstance(myContext, false);
+ ParserState parserState = ParserState.getPreTagListInstance(myContext, false, getErrorHandler());
return doXmlLoop(streamReader, parserState);
}
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
index ff57ec9b322..8cd975c631a 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser/XmlParserTest.java
@@ -76,6 +76,40 @@ public class XmlParserTest {
private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserTest.class);
+ @Test
+ public void testParseErrorHandlerNoError() {
+ String input = "";
+ ourCtx.newXmlParser().setParserErrorHandler(new StrictErrorHandler()).parseResource(Patient.class, input);
+ }
+
+ @Test
+ public void testParseErrorHandlerUnexpectedElement() {
+ String input = "";
+ try {
+ ourCtx.newXmlParser().setParserErrorHandler(new StrictErrorHandler()).parseResource(Patient.class, input);
+ fail();
+ } catch (DataFormatException e) {
+ assertThat(e.getMessage(), containsString("'foo'"));
+ }
+
+ Patient p = ourCtx.newXmlParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(Patient.class, input);
+ assertEquals(p.getName().get(0).getFamily().get(0).getValue(), "AAA");
+ }
+
+ @Test
+ public void testParseErrorHandlerUnexpectedAttribute() {
+ String input = "";
+ try {
+ ourCtx.newXmlParser().setParserErrorHandler(new StrictErrorHandler()).parseResource(Patient.class, input);
+ fail();
+ } catch (DataFormatException e) {
+ assertThat(e.getMessage(), containsString("'foo'"));
+ }
+
+ Patient p = ourCtx.newXmlParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(Patient.class, input);
+ assertEquals(p.getName().get(0).getFamily().get(0).getValue(), "AAA");
+ }
+
/**
* see #144 and #146
*/
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index ec2d533650d..e2c98ddb6e9 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -36,6 +36,12 @@
Add better addXXX() methods to structures, which take the datatype being added as a parameter. Thanks to Claude Nanjo for the
suggestion!
+
+ Add a new parser validation mechanism (see the
+ validation page]]> for info) which can be
+ used to validate resources as they are being parsed, and optionally fail if invalid/unexpected
+ elements are found in resource bodies during parsing.
+
diff --git a/src/site/resources/hapi.css b/src/site/resources/hapi.css
index e43f1541e62..dbd56346e4a 100644
--- a/src/site/resources/hapi.css
+++ b/src/site/resources/hapi.css
@@ -43,6 +43,14 @@
*/
+.doc_info_bubble {
+ border: 1px dashed #808080;
+ border-radius: 6px;
+ padding: 8px;
+ margin: 8px;
+ background: #EEE;
+}
+
TABLE.pagenavlinks {
border: 0px;
}
diff --git a/src/site/xdoc/doc_rest_client.xml b/src/site/xdoc/doc_rest_client.xml
index f0535828447..dc9a6df84dc 100644
--- a/src/site/xdoc/doc_rest_client.xml
+++ b/src/site/xdoc/doc_rest_client.xml
@@ -105,10 +105,17 @@
for more information.
-
-
+
Note on Bundle types: As of DSTU2, FHIR defines Bundle as a resource
- instead of an Atom feed as it was in DSTU1.
+ instead of an Atom feed as it was in DSTU1. In code that was written for
+ DSTU1 you would typically use the ca.uhn.fhir.model.api.Bundle
+ class to represent a bundle, and that is that default return type for search
+ methods. If you are implemeting a DSTU2+ server, is recommended to use a
+ Bundle resource class instead (e.g. ca.uhn.fhir.model.dstu2.resource.Bundle
+ or org.hl7.fhir.instance.model.Bundle
). Many of the examples below include
+ a chained invocation similar to
+ .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
, which
+ instructs the search method which bundle type should be returned.
diff --git a/src/site/xdoc/doc_validation.xml b/src/site/xdoc/doc_validation.xml
index 71e53cab51f..28dfd436f0e 100644
--- a/src/site/xdoc/doc_validation.xml
+++ b/src/site/xdoc/doc_validation.xml
@@ -8,6 +8,57 @@
+
+
+
+ HAPI supportes two types of validation, both of which are described in the
+ sections below.
+
+
+ -
+ Parser Validation is validation at runtime during the parsing
+ of a resource. It can be used to catch input data that is impossible to
+ fit into the HAPI data model. For example, it can be used to throw exceptions
+ or display error messages if a resource being parsed contains tags for which
+ there are no appropriate fields in a HAPI data structure.
+
+ -
+ Resource Validation is validation of the parsed (or constructed) resource against
+ the official FHIR validation rules (e.g. Schema/Schematron).
+
+
+
+
+
+
+
+
+ Parser validation is controlled by calling setParserErrorHandler(IParserErrorHandler)
on
+ either the FhirContext or on individual parser instances. This method
+ takes an IParserErrorHandler
, which is a callback that
+ will be invoked any time a parse issue is detected.
+
+
+
+
+
+
+
+ There are two implementations of IParserErrorHandler
worth
+ mentioning:
+
+
+
+