Work on #152 - Improved validation framework to enable failing if unexpected elements are found
This commit is contained in:
parent
e22f52ca44
commit
834710df9b
examples/src/main/java/example
hapi-fhir-base/src/main/java/ca/uhn/fhir
context
parser
hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/parser
src
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -43,6 +43,14 @@
|
|||
|
||||
*/
|
||||
|
||||
.doc_info_bubble {
|
||||
border: 1px dashed #808080;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
margin: 8px;
|
||||
background: #EEE;
|
||||
}
|
||||
|
||||
TABLE.pagenavlinks {
|
||||
border: 0px;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
Loading…
Reference in New Issue