Work on - Improved validation framework to enable failing if unexpected elements are found

This commit is contained in:
jamesagnew 2015-05-26 21:03:49 -04:00
parent e22f52ca44
commit 834710df9b
17 changed files with 335 additions and 74 deletions

View File

@ -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

View File

@ -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

View File

@ -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<String, RuntimeResourceDefinition> myNameToResourceDefinition = Collections.emptyMap();
private volatile Map<String, Class<? extends IBaseResource>> 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<BaseRuntimeElementDefinition<?>> 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 {
* </p>
*/
public IParser newJsonParser() {
return new JsonParser(this);
return new JsonParser(this, myParserErrorHandler);
}
/**
@ -393,7 +403,7 @@ public class FhirContext {
* </p>
*/
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<Class<? extends IElement>> toElementList(Collection<Class<? extends IBaseResource>> theResourceTypes) {
if (theResourceTypes == null) {
@ -515,11 +535,4 @@ public class FhirContext {
return retVal;
}
/**
* Returns all element definitions (resources, datatypes, etc.)
*/
public Collection<BaseRuntimeElementDefinition<?>> getElementDefinitions() {
return Collections.unmodifiableCollection(myClassToElementDefinition.values());
}
}

View File

@ -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 <code>true</code> (default is <code>false</code>), 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 extends IBaseResource> T parseResource(Class<T> theResourceType, Reader theReader) throws DataFormatException {
T retVal = doParseResource(theResourceType, theReader);
@ -288,7 +301,7 @@ public abstract class BaseParser implements IParser {
List<IBase> 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;

View File

@ -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
}
}

View File

@ -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;

View File

@ -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
}
}

View File

@ -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<Bundle> state = ParserState.getPreAtomInstance(myContext, theResourceType, true);
ParserState<Bundle> 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<TagList> state = ParserState.getPreTagListInstance(myContext, true);
ParserState<TagList> state = ParserState.getPreTagListInstance(myContext, true, getErrorHandler());
state.enteringNewElement(null, resourceType);
parseChildren(object, state);

View File

@ -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);
}
}

View File

@ -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<T> {
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<T> {
myState.xmlEvent(theNextEvent);
}
public static ParserState<Bundle> getPreAtomInstance(FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode) throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theContext, theJsonMode);
public static ParserState<Bundle> getPreAtomInstance(FhirContext theContext, Class<? extends IBaseResource> theResourceType, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<Bundle> retVal = new ParserState<Bundle>(theContext, theJsonMode, theErrorHandler);
if (theContext.getVersion().getVersion() == FhirVersionEnum.DSTU1) {
retVal.push(retVal.new PreAtomState(theResourceType));
} else {
@ -226,8 +227,8 @@ class ParserState<T> {
* @param theResourceType
* May be null
*/
public static <T extends IBaseResource> ParserState<T> getPreResourceInstance(Class<T> theResourceType, FhirContext theContext, boolean theJsonMode) throws DataFormatException {
ParserState<T> retVal = new ParserState<T>(theContext, theJsonMode);
public static <T extends IBaseResource> ParserState<T> getPreResourceInstance(Class<T> theResourceType, FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) throws DataFormatException {
ParserState<T> retVal = new ParserState<T>(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<T> {
return retVal;
}
public static ParserState<TagList> getPreTagListInstance(FhirContext theContext, boolean theJsonMode) {
ParserState<TagList> retVal = new ParserState<TagList>(theContext, theJsonMode);
public static ParserState<TagList> getPreTagListInstance(FhirContext theContext, boolean theJsonMode, IParserErrorHandler theErrorHandler) {
ParserState<TagList> retVal = new ParserState<TagList>(theContext, theJsonMode, theErrorHandler);
retVal.push(retVal.new PreTagListState());
return retVal;
}
@ -795,7 +796,7 @@ class ParserState<T> {
@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<T> {
@SuppressWarnings("unused")
public void enteringNewElement(String theNamespaceURI, String theLocalPart) throws DataFormatException {
// ignore by default
myErrorHandler.unknownElement(null, theLocalPart);
}
/**
@ -1422,7 +1423,9 @@ class ParserState<T> {
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<T> {
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<T> {
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<T> {
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<T> {
}
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<T> {
@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<T> {
((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<T> {
@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;
}

View File

@ -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");
}
}

View File

@ -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<Bundle> parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false);
ParserState<Bundle> parserState = ParserState.getPreAtomInstance(myContext, theResourceType, false, getErrorHandler());
return doXmlLoop(theStreamReader, parserState);
}
@ -1045,7 +1039,7 @@ public class XmlParser extends BaseParser implements IParser {
}
private <T extends IBaseResource> T parseResource(Class<T> theResourceType, XMLEventReader theStreamReader) {
ParserState<T> parserState = ParserState.getPreResourceInstance(theResourceType, myContext, false);
ParserState<T> 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<TagList> parserState = ParserState.getPreTagListInstance(myContext, false);
ParserState<TagList> parserState = ParserState.getPreTagListInstance(myContext, false, getErrorHandler());
return doXmlLoop(streamReader, parserState);
}

View File

@ -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 = "<Patient></Patient>";
ourCtx.newXmlParser().setParserErrorHandler(new StrictErrorHandler()).parseResource(Patient.class, input);
}
@Test
public void testParseErrorHandlerUnexpectedElement() {
String input = "<Patient><foo><bar/></foo><name><family value=\"AAA\"/></name></Patient>";
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 = "<Patient><name><family foo=\"FOO\" value=\"AAA\" bar=\"BAR\"/></name></Patient>";
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
*/

View File

@ -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!
</action>
<action type="add" issue="152">
Add a new parser validation mechanism (see the
<![CDATA[<a href="./doc_validation.html">validation page</a>]]> 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.
</action>
</release>
<release version="1.0" date="2015-May-8">
<action type="add">

View File

@ -43,6 +43,14 @@
*/
.doc_info_bubble {
border: 1px dashed #808080;
border-radius: 6px;
padding: 8px;
margin: 8px;
background: #EEE;
}
TABLE.pagenavlinks {
border: 0px;
}

View File

@ -105,10 +105,17 @@
for more information.
</p>
<p>
<notclosed ! colour this section differently>
<p class="doc_info_bubble">
<b>Note on Bundle types: </b> 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 <code>ca.uhn.fhir.model.api.Bundle</code>
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. <code>ca.uhn.fhir.model.dstu2.resource.Bundle</code>
or <code>org.hl7.fhir.instance.model.Bundle</code>). Many of the examples below include
a chained invocation similar to
<code>.returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)</code>, which
instructs the search method which bundle type should be returned.
</p>
<p>

View File

@ -8,6 +8,57 @@
</properties>
<body>
<section name="Validation">
<p>
HAPI supportes two types of validation, both of which are described in the
sections below.
</p>
<ul>
<li>
<b>Parser Validation</b> 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.
</li>
<li>
<b>Resource Validation</b> is validation of the parsed (or constructed) resource against
the official FHIR validation rules (e.g. Schema/Schematron).
</li>
</ul>
</section>
<section name="Parser Validation">
<p>
Parser validation is controlled by calling <code>setParserErrorHandler(IParserErrorHandler)</code> on
either the FhirContext or on individual parser instances. This method
takes an <code>IParserErrorHandler</code>, which is a callback that
will be invoked any time a parse issue is detected.
</p>
<macro name="snippet">
<param name="id" value="basicValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
There are two implementations of <code>IParserErrorHandler</code> worth
mentioning:
</p>
<ul>
<ul>
<a href="./apidocs/ca/uhn/fhir/parser/LenientErrorHandler.html">LenientErrorHandler</a>
logs any errors but does not abort parsing.
</ul>
<ul>
<a href="./apidocs/ca/uhn/fhir/parser/StrictErrorHandler.html">StrictErrorHandler</a>
throws a <code>DataFormatException</code> if any errors are detected.
</ul>
</ul>
</section>
<!-- The body of the document contains a number of sections -->
<section name="Resource Validation">