This commit is contained in:
jamesagnew 2015-07-21 08:28:40 -04:00
parent 93c04bd939
commit 48917684fd
9 changed files with 159 additions and 19 deletions

View File

@ -4,6 +4,8 @@ import java.io.File;
import java.io.FileReader; import java.io.FileReader;
import java.util.List; import java.util.List;
import javax.servlet.ServletException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter; import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -12,6 +14,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum; import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.rest.client.IGenericClient; import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
@ -19,23 +22,54 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.SingleValidationMessage; import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult; import ca.uhn.fhir.validation.ValidationResult;
@SuppressWarnings("serial")
public class ValidatorExamples { public class ValidatorExamples {
// START SNIPPET: serverValidation
public class MyRestfulServer extends RestfulServer {
@Override
protected void initialize() throws ServletException {
// ...Configure resource providers, etc...
// Create a context, set the error handler and instruct
// the server to use it
FhirContext ctx = FhirContext.forDstu2();
ctx.setParserErrorHandler(new StrictErrorHandler());
setFhirContext(ctx);
}
}
// END SNIPPET: serverValidation
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void enableValidation() { public void enableValidation() {
// START SNIPPET: enableValidation // START SNIPPET: clientValidation
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu2();
ctx.setParserErrorHandler(new StrictErrorHandler()); ctx.setParserErrorHandler(new StrictErrorHandler());
// This client will have strict parser validation enabled // This client will have strict parser validation enabled
IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2"); IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2");
// END SNIPPET: clientValidation
// This server will have strict parser validation enabled }
RestfulServer server = new RestfulServer();
server.setFhirContext(ctx);
// END SNIPPET: enableValidation @SuppressWarnings("unused")
public void parserValidation() {
// START SNIPPET: parserValidation
FhirContext ctx = FhirContext.forDstu2();
// Create a parser and configure it to use the strict error handler
IParser parser = ctx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
// This example resource is invalid, as Patient.active can not repeat
String input = "<Patient><active value=\"true\"/><active value=\"false\"/></Patient>";
// The following will throw a DataFormatException because of the StrictErrorHandler
parser.parseResource(Patient.class, input);
// END SNIPPET: parserValidation
} }
public void validateResource() { public void validateResource() {

View File

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

View File

@ -25,6 +25,16 @@ package ca.uhn.fhir.parser;
*/ */
public interface IParserErrorHandler { public interface IParserErrorHandler {
/**
* Invoked when an element repetition (e.g. a second repetition of something) is found for a field
* which is non-repeating.
*
* @param theLocation The location in the document. WILL ALWAYS BE NULL currently, as this is not yet implemented, but this parameter is included so that locations can be added in the future without changing the API.
* @param theElementName The name of the element that was found.
* @since 1.2
*/
void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName);
/** /**
* Invoked when an unknown element is found in the document. * Invoked when an unknown element is found in the document.
* *

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.parser; package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -24,19 +26,49 @@ package ca.uhn.fhir.parser;
* The default error handler, which logs issues but does not abort parsing * The default error handler, which logs issues but does not abort parsing
* *
* @see IParser#setParserErrorHandler(IParserErrorHandler) * @see IParser#setParserErrorHandler(IParserErrorHandler)
* @see FhirContext#setParserErrorHandler(IParserErrorHandler)
*/ */
public class LenientErrorHandler implements IParserErrorHandler { public class LenientErrorHandler implements IParserErrorHandler {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LenientErrorHandler.class);
private boolean myLogErrors;
/**
* Constructor which configures this handler to log all errors
*/
public LenientErrorHandler() {
myLogErrors = true;
}
/**
* Constructor
*
* @param theLogErrors Should errors be logged?
* @since 1.2
*/
public LenientErrorHandler(boolean theLogErrors) {
myLogErrors = theLogErrors;
}
@Override @Override
public void unknownElement(IParseLocation theLocation, String theElementName) { public void unknownElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
ourLog.warn("Unknown element '{}' found while parsing", theElementName); ourLog.warn("Unknown element '{}' found while parsing", theElementName);
} }
}
@Override @Override
public void unknownAttribute(IParseLocation theLocation, String theElementName) { public void unknownAttribute(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
ourLog.warn("Unknown attribute '{}' found while parsing", theElementName); ourLog.warn("Unknown attribute '{}' found while parsing", theElementName);
} }
}
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
if (myLogErrors) {
ourLog.warn("Multiple repetitions of non-repeatable element '{}' found while parsing", theElementName);
}
}
} }

View File

@ -24,8 +24,10 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.xml.stream.events.StartElement; import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent; import javax.xml.stream.events.XMLEvent;
@ -1506,6 +1508,7 @@ class ParserState<T> {
private BaseRuntimeElementCompositeDefinition<?> myDefinition; private BaseRuntimeElementCompositeDefinition<?> myDefinition;
private IBase myInstance; private IBase myInstance;
private Set<String> myParsedNonRepeatableNames = new HashSet<String>();
public ElementCompositeState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition<?> theDef, IBase theInstance) { public ElementCompositeState(PreResourceState thePreResourceState, BaseRuntimeElementCompositeDefinition<?> theDef, IBase theInstance) {
super(thePreResourceState); super(thePreResourceState);
@ -1550,6 +1553,13 @@ class ParserState<T> {
push(new SwallowChildrenWholeState(getPreResourceState())); push(new SwallowChildrenWholeState(getPreResourceState()));
return; return;
} }
if (child.getMax() < 2 && !myParsedNonRepeatableNames.add(theChildName)) {
myErrorHandler.unexpectedRepeatingElement(null, theChildName);
push(new SwallowChildrenWholeState(getPreResourceState()));
return;
}
BaseRuntimeElementDefinition<?> target = child.getChildByName(theChildName); BaseRuntimeElementDefinition<?> target = child.getChildByName(theChildName);
if (target == null) { if (target == null) {
// This is a bug with the structures and shouldn't happen.. // This is a bug with the structures and shouldn't happen..

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.parser; package ca.uhn.fhir.parser;
import ca.uhn.fhir.context.FhirContext;
/* /*
* #%L * #%L
* HAPI FHIR - Core Library * HAPI FHIR - Core Library
@ -25,6 +27,7 @@ package ca.uhn.fhir.parser;
* issue is found while parsing. * issue is found while parsing.
* *
* @see IParser#setParserErrorHandler(IParserErrorHandler) * @see IParser#setParserErrorHandler(IParserErrorHandler)
* @see FhirContext#setParserErrorHandler(IParserErrorHandler)
*/ */
public class StrictErrorHandler implements IParserErrorHandler { public class StrictErrorHandler implements IParserErrorHandler {
@ -38,4 +41,10 @@ public class StrictErrorHandler implements IParserErrorHandler {
throw new DataFormatException("Unknown attribute '" + theAttributeName + "' found during parse"); throw new DataFormatException("Unknown attribute '" + theAttributeName + "' found during parse");
} }
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
throw new DataFormatException("Multiple repetitions of non-repeatable element '" + theElementName + "' found during parse");
}
} }

View File

@ -90,6 +90,11 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
public void unknownElement(IParseLocation theLocation, String theElementName) { public void unknownElement(IParseLocation theLocation, String theElementName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown element found: " + theElementName); oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Unknown element found: " + theElementName);
} }
@Override
public void unexpectedRepeatingElement(IParseLocation theLocation, String theElementName) {
oo.addIssue().setSeverity(IssueSeverityEnum.ERROR).setCode(IssueTypeEnum.INVALID_CONTENT).setDetails("Multiple repetitions of non-repeatable element found: " + theElementName);
}
}); });
FhirValidator validator = getContext().newValidator(); FhirValidator validator = getContext().newValidator();

View File

@ -1719,6 +1719,20 @@ public class XmlParserTest {
assertEquals(p.getName().get(0).getFamily().get(0).getValue(), "AAA"); assertEquals(p.getName().get(0).getFamily().get(0).getValue(), "AAA");
} }
@Test
public void testParseErrorHandlerDuplicateElement() {
String input = "<Patient><active value=\"true\"/><active value=\"false\"/></Patient>";
try {
ourCtx.newXmlParser().setParserErrorHandler(new StrictErrorHandler()).parseResource(Patient.class, input);
fail();
} catch (DataFormatException e) {
assertThat(e.getMessage(), containsString("Multiple repetitions"));
}
Patient p = ourCtx.newXmlParser().setParserErrorHandler(new LenientErrorHandler()).parseResource(Patient.class, input);
assertEquals("true", p.getActive().getValueAsString());
}
@Test @Test
public void testParseFeedWithListResource() throws ConfigurationException, DataFormatException, IOException { public void testParseFeedWithListResource() throws ConfigurationException, DataFormatException, IOException {

View File

@ -38,19 +38,14 @@
takes an <code>IParserErrorHandler</code>, which is a callback that takes an <code>IParserErrorHandler</code>, which is a callback that
will be invoked any time a parse issue is detected. will be invoked any time a parse issue is detected.
</p> </p>
<macro name="snippet">
<param name="id" value="basicValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p> <p>
There are two implementations of <code>IParserErrorHandler</code> worth There are two implementations of <code>IParserErrorHandler</code> worth
mentioning: mentioning.
</p> </p>
<ul> <ul>
<ul> <ul>
<a href="./apidocs/ca/uhn/fhir/parser/LenientErrorHandler.html">LenientErrorHandler</a> <a href="./apidocs/ca/uhn/fhir/parser/LenientErrorHandler.html">LenientErrorHandler</a>
logs any errors but does not abort parsing. logs any errors but does not abort parsing. This is the default.
</ul> </ul>
<ul> <ul>
<a href="./apidocs/ca/uhn/fhir/parser/StrictErrorHandler.html">StrictErrorHandler</a> <a href="./apidocs/ca/uhn/fhir/parser/StrictErrorHandler.html">StrictErrorHandler</a>
@ -58,9 +53,35 @@
</ul> </ul>
</ul> </ul>
<p>
The following example shows how to configure a parser to use strict validation.
</p>
<macro name="snippet">
<param name="id" value="parserValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
You can also configure the error handler at the FhirContext level, which is useful
for clients.
</p>
<macro name="snippet">
<param name="id" value="clientValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
<p>
FhirContext level validators can also be useful on servers.
</p>
<macro name="snippet">
<param name="id" value="serverValidation" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</section> </section>
<!-- The body of the document contains a number of sections --> <!-- RESOURCE VALIDATION -->
<section name="Resource Validation"> <section name="Resource Validation">
<p> <p>