Add configuration property to DSTU3 FhirInstanceValidator to allow client code to change unknown extension handling behaviour.

This commit is contained in:
James 2017-04-23 19:28:45 -04:00
parent 9c595e18f9
commit bb9cd7c198
12 changed files with 579 additions and 362 deletions

View File

@ -8,21 +8,23 @@ 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.hapi.validation.DefaultProfileValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator; import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport; import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain; import org.hl7.fhir.dstu3.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.dstu3.model.StringType;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.instance.model.ValueSet; import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.IParser; 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;
@ -39,7 +41,7 @@ public class ValidatorExamples {
public void validationIntro() { public void validationIntro() {
// START SNIPPET: validationIntro // START SNIPPET: validationIntro
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Ask the context for a validator // Ask the context for a validator
FhirValidator validator = ctx.newValidator(); FhirValidator validator = ctx.newValidator();
@ -74,7 +76,7 @@ public class ValidatorExamples {
// Create a context, set the error handler and instruct // Create a context, set the error handler and instruct
// the server to use it // the server to use it
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
ctx.setParserErrorHandler(new StrictErrorHandler()); ctx.setParserErrorHandler(new StrictErrorHandler());
setFhirContext(ctx); setFhirContext(ctx);
} }
@ -85,19 +87,19 @@ public class ValidatorExamples {
@SuppressWarnings("unused") @SuppressWarnings("unused")
public void enableValidation() { public void enableValidation() {
// START SNIPPET: clientValidation // START SNIPPET: clientValidation
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
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/baseDstu3");
// END SNIPPET: clientValidation // END SNIPPET: clientValidation
} }
public void parserValidation() { public void parserValidation() {
// START SNIPPET: parserValidation // START SNIPPET: parserValidation
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Create a parser and configure it to use the strict error handler // Create a parser and configure it to use the strict error handler
IParser parser = ctx.newXmlParser(); IParser parser = ctx.newXmlParser();
@ -114,13 +116,13 @@ public class ValidatorExamples {
public void validateResource() { public void validateResource() {
// START SNIPPET: basicValidation // START SNIPPET: basicValidation
// As always, you need a context // As always, you need a context
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Create and populate a new patient object // Create and populate a new patient object
Patient p = new Patient(); Patient p = new Patient();
p.addName().addFamily("Smith").addGiven("John").addGiven("Q"); p.addName().setFamily("Smith").addGiven("John").addGiven("Q");
p.addIdentifier().setSystem("urn:foo:identifiers").setValue("12345"); p.addIdentifier().setSystem("urn:foo:identifiers").setValue("12345");
p.addTelecom().setSystem(ContactPointSystemEnum.PHONE).setValue("416 123-4567"); p.addTelecom().setSystem(ContactPointSystem.PHONE).setValue("416 123-4567");
// Request a validator and apply it // Request a validator and apply it
FhirValidator val = ctx.newValidator(); FhirValidator val = ctx.newValidator();
@ -168,20 +170,27 @@ public class ValidatorExamples {
private static void instanceValidator() throws Exception { private static void instanceValidator() throws Exception {
// START SNIPPET: instanceValidator // START SNIPPET: instanceValidator
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Create a FhirInstanceValidator and register it to a validator // Create a FhirInstanceValidator and register it to a validator
FhirValidator validator = ctx.newValidator(); FhirValidator validator = ctx.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(); FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
validator.registerValidatorModule(instanceValidator); validator.registerValidatorModule(instanceValidator);
/*
* If you want, you can configure settings on the validator to adjust
* its behaviour during validation
*/
instanceValidator.setAnyExtensionsAllowed(true);
/* /*
* Let's create a resource to validate. This Observation has some fields * Let's create a resource to validate. This Observation has some fields
* populated, but it is missing Observation.status, which is mandatory. * populated, but it is missing Observation.status, which is mandatory.
*/ */
Observation obs = new Observation(); Observation obs = new Observation();
obs.getCode().addCoding().setSystem("http://loinc.org").setCode("12345-6"); obs.getCode().addCoding().setSystem("http://loinc.org").setCode("12345-6");
obs.setValue(new StringDt("This is a value")); obs.setValue(new StringType("This is a value"));
// Validate // Validate
ValidationResult result = validator.validateWithResult(obs); ValidationResult result = validator.validateWithResult(obs);
@ -205,7 +214,7 @@ public class ValidatorExamples {
private static void instanceValidatorCustom() throws Exception { private static void instanceValidatorCustom() throws Exception {
// START SNIPPET: instanceValidatorCustom // START SNIPPET: instanceValidatorCustom
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Create a FhirInstanceValidator and register it to a validator // Create a FhirInstanceValidator and register it to a validator
FhirValidator validator = ctx.newValidator(); FhirValidator validator = ctx.newValidator();
@ -213,36 +222,48 @@ public class ValidatorExamples {
validator.registerValidatorModule(instanceValidator); validator.registerValidatorModule(instanceValidator);
IValidationSupport valSupport = new IValidationSupport() { IValidationSupport valSupport = new IValidationSupport() {
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
// TODO: Implement
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
// TODO: Implement
return false;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
// TODO: Implement
return null;
}
@Override
public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) {
// TODO: Implement
return null;
}
@Override @Override
public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { public org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent theInclude) {
// TODO: Implement // TODO: implement
return null; return null;
} }
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
// TODO: implement
return null;
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
// TODO: implement
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
// TODO: implement
return null;
}
@Override
public StructureDefinition fetchStructureDefinition(FhirContext theCtx, String theUrl) {
// TODO: implement
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
// TODO: implement
return false;
}
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
// TODO: implement
return null;
}
}; };
/* /*
@ -261,7 +282,7 @@ public class ValidatorExamples {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private static void validateFiles() throws Exception { private static void validateFiles() throws Exception {
// START SNIPPET: validateFiles // START SNIPPET: validateFiles
FhirContext ctx = FhirContext.forDstu2(); FhirContext ctx = FhirContext.forDstu3();
// Create a validator and configure it // Create a validator and configure it
FhirValidator validator = ctx.newValidator(); FhirValidator validator = ctx.newValidator();

View File

@ -130,6 +130,16 @@
<artifactId>junit</artifactId> <groupId>junit</groupId> </exclusion> <exclusion> <artifactId>jdom</artifactId> <groupId>org.jdom</groupId> </exclusion> <exclusion> <artifactId>gson</artifactId> <groupId>com.google.code.gson</groupId> <artifactId>junit</artifactId> <groupId>junit</groupId> </exclusion> <exclusion> <artifactId>jdom</artifactId> <groupId>org.jdom</groupId> </exclusion> <exclusion> <artifactId>gson</artifactId> <groupId>com.google.code.gson</groupId>
</exclusion> </exclusions> </dependency> --> </exclusion> </exclusions> </dependency> -->
<!--
For some reason JavaDoc crashed during site generation unless we have this dependency
-->
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- Test Database --> <!-- Test Database -->
<dependency> <dependency>

View File

@ -1083,8 +1083,8 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
* *
* @param theEntity * @param theEntity
* The entity being updated (Do not modify the entity! Undefined behaviour will occur!) * The entity being updated (Do not modify the entity! Undefined behaviour will occur!)
* @param theTag * @param theResource
* The tag * The resource being persisted
*/ */
protected void postPersist(ResourceTable theEntity, T theResource) { protected void postPersist(ResourceTable theEntity, T theResource) {
// nothing // nothing

View File

@ -163,7 +163,6 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* @param theId * @param theId
* @param theRequestDetails * @param theRequestDetails
* TODO * TODO
* @return
* @throws ResourceNotFoundException * @throws ResourceNotFoundException
* If the ID is not known to the server * If the ID is not known to the server
*/ */

View File

@ -37,216 +37,235 @@ import ca.uhn.fhir.validation.IValidatorModule;
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule { public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private BestPracticeWarningLevel myBestPracticeWarningLevel; private boolean myAnyExtensionsAllowed = true;
private DocumentBuilderFactory myDocBuilderFactory; private BestPracticeWarningLevel myBestPracticeWarningLevel;
private StructureDefinition myStructureDefintion; private DocumentBuilderFactory myDocBuilderFactory;
private IValidationSupport myValidationSupport; private StructureDefinition myStructureDefintion;
/** private IValidationSupport myValidationSupport;
* Constructor
*
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
}
/** /**
* Constructor which uses the given validation support * Constructor
* *
* @param theValidationSupport * Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
* The validation support */
*/ public FhirInstanceValidator() {
public FhirInstanceValidator(IValidationSupport theValidationSupport) { this(new DefaultProfileValidationSupport());
myDocBuilderFactory = DocumentBuilderFactory.newInstance(); }
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
private String determineResourceName(Document theDocument) { /**
Element root = null; * Constructor which uses the given validation support
*
* @param theValidationSupport
* The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
NodeList list = theDocument.getChildNodes(); private String determineResourceName(Document theDocument) {
for (int i = 0; i < list.getLength(); i++) { Element root = null;
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) { NodeList list = theDocument.getChildNodes();
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName; for (int i = 0; i < list.getLength(); i++) {
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName); if (list.item(i) instanceof Element) {
return profile; root = (Element) list.item(i);
} break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
/** private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}). String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
* <p> StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is return profile;
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be }
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
}
/** /**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of * Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. * <p>
*/ * The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
public IValidationSupport getValidationSupport() { * set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
return myValidationSupport; * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
} * guielines will be ignored.
* </p>
*
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
}
/** /**
* Sets the "best practice warning level". When validating, any deviations from best practices will be reported at * Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* this level. * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
* <p> */
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is public IValidationSupport getValidationSupport() {
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be return myValidationSupport;
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice }
* guielines will be ignored.
* </p>
*
* @param theBestPracticeWarningLevel
* The level, must not be <code>null</code>
*/
public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
Validate.notNull(theBestPracticeWarningLevel);
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) { /**
myStructureDefintion = theStructureDefintion; * If set to {@literal true} (default is true) extensions which are not known to the
} * validator (e.g. because they have not been explicitly declared in a profile) will
* be validated but will not cause an error.
*/
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
/** /**
* Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of * If set to {@literal true} (default is true) extensions which are not known to the
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used. * validator (e.g. because they have not been explicitly declared in a profile) will
*/ * be validated but will not cause an error.
public void setValidationSupport(IValidationSupport theValidationSupport) { */
myValidationSupport = theValidationSupport; public void setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
} myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) { /**
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport); * Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
* this level.
* <p>
* The FHIR Instance Validator has a number of checks for best practices in terms of FHIR usage. If this setting is
* set to {@link BestPracticeWarningLevel#Error}, any resource data which does not meet these best practices will be
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored.
* </p>
*
* @param theBestPracticeWarningLevel
* The level, must not be <code>null</code>
*/
public void setBestPracticeWarningLevel(BestPracticeWarningLevel theBestPracticeWarningLevel) {
Validate.notNull(theBestPracticeWarningLevel);
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
}
InstanceValidator v; public void setStructureDefintion(StructureDefinition theStructureDefintion) {
IEvaluationContext evaluationCtx = new NullEvaluationContext(); myStructureDefintion = theStructureDefintion;
try { }
v = new InstanceValidator(workerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel); /**
v.setAnyExtensionsAllowed(true); * Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
v.setResourceIdRule(IdStatus.OPTIONAL); * {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
if (theEncoding == EncodingEnum.XML) { InstanceValidator v;
Document document; IEvaluationContext evaluationCtx = new NullEvaluationContext();
try { try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder(); v = new InstanceValidator(workerContext, evaluationCtx);
InputSource src = new InputSource(new StringReader(theInput)); } catch (Exception e) {
document = builder.parse(src); throw new ConfigurationException(e);
} catch (Exception e2) { }
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
String resourceName = determineResourceName(document); v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName); v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
if (profile != null) { v.setResourceIdRule(IdStatus.OPTIONAL);
try {
v.validate(null, messages, document, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
String resourceName = json.get("resourceType").getAsString(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
for (int i = 0; i < messages.size(); i++) { if (theEncoding == EncodingEnum.XML) {
ValidationMessage next = messages.get(i); Document document;
if ("Binding has no source, so can't be checked".equals(next.getMessage())) { try {
messages.remove(i); DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
i--; InputSource src = new InputSource(new StringReader(theInput));
} document = builder.parse(src);
} } catch (Exception e2) {
return messages; ourLog.error("Failure to parse XML input", e2);
} ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
@Override String resourceName = determineResourceName(document);
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) { StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding()); if (profile != null) {
} try {
v.validate(null, messages, document, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
public class NullEvaluationContext implements IEvaluationContext { String resourceName = json.get("resourceType").getAsString();
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile);
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
@Override for (int i = 0; i < messages.size(); i++) {
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException { ValidationMessage next = messages.get(i);
return null; if ("Binding has no source, so can't be checked".equals(next.getMessage())) {
} messages.remove(i);
i--;
}
}
return messages;
}
@Override @Override
public List<Base> executeFunction(Object theAppContext, String theFunctionName, List<List<Base>> theParameters) { protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return null; return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
} }
@Override public class NullEvaluationContext implements IEvaluationContext {
public boolean log(String theArgument, List<Base> theFocus) {
return false;
}
@Override @Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException { public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException {
return null; return null;
} }
@Override @Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException { public List<Base> executeFunction(Object theAppContext, String theFunctionName, List<List<Base>> theParameters) {
return null; return null;
} }
@Override @Override
public FunctionDetails resolveFunction(String theFunctionName) { public boolean log(String theArgument, List<Base> theFocus) {
return null; return false;
} }
@Override @Override
public Base resolveReference(Object theAppContext, String theUrl) { public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException {
return null; return null;
} }
} @Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException {
return null;
}
@Override
public FunctionDetails resolveFunction(String theFunctionName) {
return null;
}
@Override
public Base resolveReference(Object theAppContext, String theUrl) {
return null;
}
}
} }

View File

@ -7,6 +7,7 @@ import java.util.List;
import org.hl7.fhir.dstu3.elementmodel.Element; import org.hl7.fhir.dstu3.elementmodel.Element;
import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat; import org.hl7.fhir.dstu3.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.utils.IResourceValidator.ReferenceValidationPolicy;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
@ -23,8 +24,26 @@ import com.google.gson.JsonObject;
*/ */
public interface IResourceValidator { public interface IResourceValidator {
public enum ReferenceValidationPolicy {
IGNORE, CHECK_TYPE_IF_EXISTS, CHECK_EXISTS, CHECK_EXISTS_AND_TYPE, CHECK_VALID;
public boolean checkExists() {
return this == CHECK_EXISTS_AND_TYPE || this == CHECK_EXISTS || this == CHECK_VALID;
}
public boolean checkType() {
return this == CHECK_TYPE_IF_EXISTS || this == CHECK_EXISTS_AND_TYPE || this == CHECK_VALID;
}
public boolean checkValid() {
return this == CHECK_VALID;
}
}
public interface IValidatorResourceFetcher { public interface IValidatorResourceFetcher {
Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, IOException; Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, IOException, FHIRException;
ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url);
boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException;
} }
public enum BestPracticeWarningLevel { public enum BestPracticeWarningLevel {
@ -46,6 +65,7 @@ public interface IResourceValidator {
OPTIONAL, REQUIRED, PROHIBITED OPTIONAL, REQUIRED, PROHIBITED
} }
/** /**
* how much to check displays for coded elements * how much to check displays for coded elements
@ -69,20 +89,19 @@ public interface IResourceValidator {
* *
*/ */
BestPracticeWarningLevel getBasePracticeWarningLevel(); BestPracticeWarningLevel getBasePracticeWarningLevel();
void setBestPracticeWarningLevel(BestPracticeWarningLevel value); IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value);
IValidatorResourceFetcher getFetcher(); IValidatorResourceFetcher getFetcher();
void setFetcher(IValidatorResourceFetcher value); IResourceValidator setFetcher(IValidatorResourceFetcher value);
boolean isNoBindingMsgSuppressed(); boolean isNoBindingMsgSuppressed();
void setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed); IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed);
public boolean isNoInvariantChecks(); public boolean isNoInvariantChecks();
public void setNoInvariantChecks(boolean value) ; public IResourceValidator setNoInvariantChecks(boolean value) ;
public boolean isNoTerminologyChecks(); public boolean isNoTerminologyChecks();
public void setNoTerminologyChecks(boolean noTerminologyChecks); public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks);
/** /**
* Whether being unable to resolve a profile in found in Resource.meta.profile or ElementDefinition.type.profile or targetProfile is an error or just a warning * Whether being unable to resolve a profile in found in Resource.meta.profile or ElementDefinition.type.profile or targetProfile is an error or just a warning

View File

@ -11,6 +11,8 @@ 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 java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.conformance.ProfileUtilities; import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
@ -82,6 +84,7 @@ import org.hl7.fhir.dstu3.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine; import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.dstu3.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.dstu3.utils.IResourceValidator; import org.hl7.fhir.dstu3.utils.IResourceValidator;
import org.hl7.fhir.dstu3.utils.ToolingExtensions;
import org.hl7.fhir.dstu3.utils.ValidationProfileSet; import org.hl7.fhir.dstu3.utils.ValidationProfileSet;
import org.hl7.fhir.dstu3.utils.ValidationProfileSet.ProfileRegistration; import org.hl7.fhir.dstu3.utils.ValidationProfileSet.ProfileRegistration;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
@ -151,6 +154,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noBindingMsgSuppressed; private boolean noBindingMsgSuppressed;
private HashMap<Element, ResourceProfiles> resourceProfilesMap; private HashMap<Element, ResourceProfiles> resourceProfilesMap;
private IValidatorResourceFetcher fetcher; private IValidatorResourceFetcher fetcher;
long time = 0;
/* /*
* Keeps track of whether a particular profile has been checked or not yet * Keeps track of whether a particular profile has been checked or not yet
@ -287,23 +291,33 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
fpe.setHostServices(hostServices); fpe.setHostServices(hostServices);
source = Source.InstanceValidator; source = Source.InstanceValidator;
} }
public InstanceValidator(ValidationEngine engine) {
super();
this.context = engine.getContext();
fpe = engine.getFpe();
source = Source.InstanceValidator;
}
@Override @Override
public boolean isNoInvariantChecks() { public boolean isNoInvariantChecks() {
return noInvariantChecks; return noInvariantChecks;
} }
@Override @Override
public void setNoInvariantChecks(boolean value) { public IResourceValidator setNoInvariantChecks(boolean value) {
this.noInvariantChecks = value; this.noInvariantChecks = value;
return this;
} }
public IValidatorResourceFetcher getFetcher() { public IValidatorResourceFetcher getFetcher() {
return this.fetcher; return this.fetcher;
} }
public void setFetcher(IValidatorResourceFetcher value) { public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
this.fetcher = value; this.fetcher = value;
return this;
} }
private boolean allowUnknownExtension(String url) { private boolean allowUnknownExtension(String url) {
@ -788,6 +802,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference"); rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Coding.system must be an absolute reference, not a local reference");
if (system != null && code != null && !noTerminologyChecks) { if (system != null && code != null && !noTerminologyChecks) {
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(system), "The Coding references a value set, not a code system (\""+system+"\")");
try { try {
if (checkCode(errors, element, path, code, system, display)) if (checkCode(errors, element, path, code, system, display))
if (theElementCntext != null && theElementCntext.hasBinding()) { if (theElementCntext != null && theElementCntext.hasBinding()) {
@ -841,6 +856,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
private boolean isValueSet(String url) {
try {
ValueSet vs = context.fetchResourceWithException(ValueSet.class, url);
return vs != null;
} catch (Exception e) {
return false;
}
}
private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) { private void checkContactPoint(List<ValidationMessage> errors, String path, Element focus, ContactPoint fixed) {
checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus);
@ -1156,12 +1180,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus); checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus);
} }
private void checkPrimitive(List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) throws FHIRException { private void checkPrimitive(Object appContext, List<ValidationMessage> errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) throws FHIRException, IOException {
if (isBlank(e.primitiveValue())) { if (isBlank(e.primitiveValue())) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "primitive types must have a value or must have child extensions"); rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "primitive types must have a value or must have child extensions");
return; return;
} }
if (type.equals("boolean")) { String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex!=null)
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), "Element value '" + e.primitiveValue() + "' does not meet regex '" + regex + "'");
if (type.equals("boolean")) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'"); rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()), "boolean values must be 'true' or 'false'");
} }
if (type.equals("uri")) { if (type.equals("uri")) {
@ -1169,6 +1197,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:"); rule(errors, IssueType.INVALID, e.line(), e.col(), path, !e.primitiveValue().startsWith("uuid:"), "URI values cannot start with uuid:");
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace"); rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().equals(e.primitiveValue().trim()), "URI values cannot have leading or trailing whitespace");
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength()); rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength()==0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
// now, do we check the URI target?
if (fetcher != null) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, fetcher.resolveURL(appContext, path, e.primitiveValue()), "URL value '"+e.primitiveValue()+"' does not resolve");
}
} }
if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) { if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) { if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) {
@ -1345,58 +1378,81 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return; return;
} }
String refType = ref.startsWith("#")? "contained": (localResolve(ref, stack, errors, path)!=null ? "bundle" : "remote"); Element we = localResolve(ref, stack, errors, path);
Element we = resolve(appContext, ref, stack, errors, path); String refType;
if (ref.startsWith("#")) {
refType = "contained";
} else {
if (we == null) {
refType = "remote";
} else {
refType = "bundle";
}
}
String ft; String ft;
if (we != null) if (we != null)
ft = we.getType(); ft = we.getType();
else else
ft = tryParse(ref); ft = tryParse(ref);
ReferenceValidationPolicy pol = refType.equals("contained") ? ReferenceValidationPolicy.CHECK_VALID : fetcher == null ? ReferenceValidationPolicy.IGNORE : fetcher.validationPolicy(appContext, path, ref);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, we!=null || !refType.equals("contained"), "Unable to resolve contained resource"); if (pol.checkExists()) {
if (hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) { if (we == null) {
boolean ok = false; if (fetcher == null)
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); throw new FHIRException("Resource resolution services not provided");
for (TypeRefComponent type : container.getType()) { we = fetcher.fetch(appContext, ref);
if (!ok && type.getCode().equals("Reference")) { }
// we validate as much as we can. First, can we infer a type from the profile? (Need to change this to targetProfile when Grahame's ready) rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, we != null, "Unable to resolve resource '"+ref+"'");
if (!type.hasTargetProfile() || type.getTargetProfile().equals("http://hl7.org/fhir/StructureDefinition/Resource")) }
ok = true;
else {
String pr = type.getTargetProfile(); // Need to change to targetProfile when Grahame's ready
String bt = getBaseType(profile, pr); if (we != null && pol.checkType()) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt); if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) {
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) { boolean ok = false;
b.append(bt); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
ok = bt.equals(ft); for (TypeRefComponent type : container.getType()) {
if (ok && we!=null) { if (!ok && type.getCode().equals("Reference")) {
doResourceProfile(appContext, we, pr, errors, stack.push(we, -1, null, null), path, element); // we validate as much as we can. First, can we infer a type from the profile?
if (!type.hasTargetProfile() || type.getTargetProfile().equals("http://hl7.org/fhir/StructureDefinition/Resource"))
ok = true;
else {
String pr = type.getTargetProfile();
String bt = getBaseType(profile, pr);
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + bt);
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, bt != null, "Unable to resolve the profile reference '" + pr + "'")) {
b.append(bt);
ok = bt.equals(ft);
if (ok && we!=null && pol.checkValid()) {
doResourceProfile(appContext, we, pr, errors, stack.push(we, -1, null, null), path, element);
}
} else
ok = true; // suppress following check
if (ok && type.hasAggregation()) {
boolean modeOk;
for (Enumeration<AggregationMode> mode : type.getAggregation()) {
if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
ok = true;
else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
ok = true;
else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote")))
ok = true;
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
} }
} else
ok = true; // suppress following check
if (ok && type.hasAggregation()) {
boolean modeOk;
for (Enumeration<AggregationMode> mode : type.getAggregation()) {
if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
ok = true;
else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
ok = true;
else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled")||refType.equals("remote")))
ok = true;
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
} }
} }
if (!ok && type.getCode().equals("*")) {
ok = true; // can refer to anything
}
} }
if (!ok && type.getCode().equals("*")) { rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")");
ok = true; // can refer to anything
}
} }
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + b.toString() + ")"); }
if (pol == ReferenceValidationPolicy.CHECK_VALID) {
// todo....
} }
} }
private void doResourceProfile(Object appContext, Element resource, String profile, List<ValidationMessage> errors, NodeStack stack, String path, Element element) throws FHIRException, IOException { private void doResourceProfile(Object appContext, Element resource, String profile, List<ValidationMessage> errors, NodeStack stack, String path, Element element) throws FHIRException, IOException {
ResourceProfiles resourceProfiles = addResourceProfile(errors, resource, profile, path, element, stack); ResourceProfiles resourceProfiles = addResourceProfile(errors, resource, profile, path, element, stack);
if (resourceProfiles.isProcessed()) { if (resourceProfiles.isProcessed()) {
@ -1571,27 +1627,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return element; return element;
List<String> nodes = new ArrayList<String>(); List<String> nodes = new ArrayList<String>();
String s = discriminator; Matcher matcher = Pattern.compile("([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?)(\\.([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?))*").matcher(discriminator);
do { while (matcher.find()) {
String node = null; if (!matcher.group(1).startsWith("@"))
if (s.contains(".")) { nodes.add(matcher.group(1));
node = s.substring(0, s.indexOf('.')); if (matcher.groupCount()>4 && matcher.group(4)!= null && !matcher.group(4).startsWith("@"))
if (node.contains("(")) { nodes.add(matcher.group(4));
if (!s.contains(")")) }
throw new DefinitionException("Discriminator has open bracket but no closing bracket: " + discriminator);
node = s.substring(0,s.indexOf(')') + 1);
s = s.substring(s.indexOf(")") + 1);
if (s.startsWith("."))
s = s.substring(1);
} else
s = s.substring(s.indexOf('.') + 1);
} else {
node = s;
s = null;
}
if (!node.startsWith("@"))
nodes.add(node);
} while (s !=null && !s.isEmpty());
for (String fullnode : nodes) { for (String fullnode : nodes) {
String node = fullnode.contains("(") ? fullnode.substring(0, fullnode.indexOf('(')) : fullnode; String node = fullnode.contains("(") ? fullnode.substring(0, fullnode.indexOf('(')) : fullnode;
@ -2015,7 +2057,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return null; return null;
} }
private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws FHIRFormatError, DefinitionException, IOException { private Element resolve(Object appContext, String ref, NodeStack stack, List<ValidationMessage> errors, String path) throws IOException, FHIRException {
Element local = localResolve(ref, stack, errors, path); Element local = localResolve(ref, stack, errors, path);
if (local!=null) if (local!=null)
return local; return local;
@ -2149,8 +2191,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
this.anyExtensionsAllowed = anyExtensionsAllowed; this.anyExtensionsAllowed = anyExtensionsAllowed;
} }
public void setBestPracticeWarningLevel(BestPracticeWarningLevel value) { public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
bpWarnings = value; bpWarnings = value;
return this;
} }
@Override @Override
@ -2197,8 +2240,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* @throws IOException * @throws IOException
* @throws FHIRException * @throws FHIRException
*/ */
private boolean sliceMatches(Object appContext, Element element, String path, ElementDefinition slice, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException, IOException { private boolean sliceMatches(Object appContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, NodeStack stack) throws DefinitionException, FHIRException, IOException {
if (!slice.getSlicing().hasDiscriminator()) if (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case return false; // cannot validate in this case
ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache"); ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache");
@ -2206,7 +2249,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime(); long t = System.nanoTime();
// GG: this approach is flawed because it treats discriminators individually rather than collectively // GG: this approach is flawed because it treats discriminators individually rather than collectively
String expression = "true"; String expression = "true";
for (ElementDefinitionSlicingDiscriminatorComponent s : slice.getSlicing().getDiscriminator()) { for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
String discriminator = s.getPath(); String discriminator = s.getPath();
if (s.getType() == DiscriminatorType.PROFILE) if (s.getType() == DiscriminatorType.PROFILE)
throw new FHIRException("Validating against slices with discriminators based on profiles is not yet supported by the FHIRPath engine: " + discriminator); throw new FHIRException("Validating against slices with discriminators based on profiles is not yet supported by the FHIRPath engine: " + discriminator);
@ -2226,7 +2269,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
type = criteriaElement.getType().get(0).getCode(); type = criteriaElement.getType().get(0).getCode();
} }
if (type==null) if (type==null)
throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + slice.getSliceName() + " does not declare a type"); // Slicer won't ever have a slice name, so this error needs to be reworked
throw new DefinitionException("Discriminator (" + discriminator + ") is based on type, but slice " + slicer.getSliceName() + " does not declare a type");
if (discriminator.isEmpty()) if (discriminator.isEmpty())
expression = expression + " and this is " + type; expression = expression + " and this is " + type;
else else
@ -2249,7 +2293,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (fixed instanceof BooleanType) { } else if (fixed instanceof BooleanType) {
expression = expression + ((BooleanType)fixed).asStringValue(); expression = expression + ((BooleanType)fixed).asStringValue();
} else } else
throw new DefinitionException("Unsupported fixed value type for discriminator(" + discriminator + ") for slice " + slice.getSliceName() + ": " + fixed.getClass().getName()); throw new DefinitionException("Unsupported fixed value type for discriminator(" + discriminator + ") for slice " + slicer.getSliceName() + ": " + fixed.getClass().getName());
} else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().getValueSetReference()!=null) { } else if (criteriaElement.hasBinding() && criteriaElement.getBinding().hasStrength() && criteriaElement.getBinding().getStrength().equals(BindingStrength.REQUIRED) && criteriaElement.getBinding().getValueSetReference()!=null) {
expression = expression + " and " + discriminator + " in '" + criteriaElement.getBinding().getValueSetReference().getReference() + "'"; expression = expression + " and " + discriminator + " in '" + criteriaElement.getBinding().getValueSetReference().getReference() + "'";
} else if (criteriaElement.hasMin() && criteriaElement.getMin()>0) { } else if (criteriaElement.hasMin() && criteriaElement.getMin()>0) {
@ -2257,7 +2301,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) { } else if (criteriaElement.hasMax() && criteriaElement.getMax().equals("0")) {
expression = expression + " and " + discriminator + ".exists().not()"; expression = expression + " and " + discriminator + ".exists().not()";
} else { } else {
throw new DefinitionException("Could not match discriminator (" + discriminator + ") for slice " + slice.getSliceName() + " - does not have fixed value, binding or existence assertions"); throw new DefinitionException("Could not match discriminator (" + discriminator + ") for slice " + slicer.getSliceName() + " - does not have fixed value, binding or existence assertions");
} }
} }
@ -2893,7 +2937,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// String firstBase = null; // String firstBase = null;
// firstBase = ebase == null ? base : ebase; // firstBase = ebase == null ? base : ebase;
long time = 0;
private void validateElement(Object appContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context, private void validateElement(Object appContext, List<ValidationMessage> errors, StructureDefinition profile, ElementDefinition definition, StructureDefinition cprofile, ElementDefinition context,
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException, IOException { Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept) throws FHIRException, FHIRException, IOException {
element.markValidation(profile, definition); element.markValidation(profile, definition);
@ -2932,7 +2975,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// 2. assign children to a definition // 2. assign children to a definition
// for each definition, for each child, check whether it belongs in the slice // for each definition, for each child, check whether it belongs in the slice
ElementDefinition slice = null; ElementDefinition slicer = null;
boolean unsupportedSlicing = false; boolean unsupportedSlicing = false;
List<String> problematicPaths = new ArrayList<String>(); List<String> problematicPaths = new ArrayList<String>();
String slicingPath = null; String slicingPath = null;
@ -2949,26 +2992,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
slicingPath = null; slicingPath = null;
// where are we with slicing // where are we with slicing
if (ed.hasSlicing()) { if (ed.hasSlicing()) {
if (slice != null && slice.getPath().equals(ed.getPath())) if (slicer != null && slicer.getPath().equals(ed.getPath()))
throw new DefinitionException("Slice encountered midway through path on " + slice.getPath()); throw new DefinitionException("Slice encountered midway through path on " + slicer.getPath());
slice = ed; slicer = ed;
process = false; process = false;
sliceOffset = i; sliceOffset = i;
} else if (slice != null && !slice.getPath().equals(ed.getPath())) } else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
slice = null; slicer = null;
// if (process) { // if (process) {
for (ElementInfo ei : children) { for (ElementInfo ei : children) {
boolean match = false; boolean match = false;
if (slice == null || slice == ed) { if (slicer == null || slicer == ed) {
match = nameMatches(ei.name, tail(ed.getPath())); match = nameMatches(ei.name, tail(ed.getPath()));
} else { } else {
// ei.slice = slice; // ei.slice = slice;
if (nameMatches(ei.name, tail(ed.getPath()))) if (nameMatches(ei.name, tail(ed.getPath())))
try { try {
match = sliceMatches(appContext, ei.element, ei.path, slice, ed, profile, errors, stack); match = sliceMatches(appContext, ei.element, ei.path, slicer, ed, profile, errors, stack);
if (match) if (match)
ei.slice = slice; ei.slice = slicer;
} catch (FHIRException e) { } catch (FHIRException e) {
warning(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.path, false, e.getMessage()); warning(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.path, false, e.getMessage());
unsupportedSlicing = true; unsupportedSlicing = true;
@ -2976,7 +3019,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
if (match) { if (match) {
if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null || ei.definition == slice, "Profile " + profile.getUrl() + ", Element matches more than one slice")) { if (rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition == null || ei.definition == slicer, "Profile " + profile.getUrl() + ", Element matches more than one slice")) {
ei.definition = ed; ei.definition = ed;
if (ei.slice == null) { if (ei.slice == null) {
ei.index = i; ei.index = i;
@ -2995,8 +3038,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
int lastSlice = -1; int lastSlice = -1;
for (ElementInfo ei : children) { for (ElementInfo ei : children) {
String sliceInfo = ""; String sliceInfo = "";
if (slice != null) if (slicer != null)
sliceInfo = " (slice: " + slice.getPath()+")"; sliceInfo = " (slice: " + slicer.getPath()+")";
if (ei.path.endsWith(".extension")) if (ei.path.endsWith(".extension"))
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition != null, "Element is unknown or does not match any slice (url=\"" + ei.element.getNamedChildValue("url") + "\")" + (profile==null ? "" : " for profile " + profile.getUrl())); rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.path, ei.definition != null, "Element is unknown or does not match any slice (url=\"" + ei.element.getNamedChildValue("url") + "\")" + (profile==null ? "" : " for profile " + profile.getUrl()));
else if (!unsupportedSlicing) else if (!unsupportedSlicing)
@ -3131,7 +3174,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type != null) { if (type != null) {
if (isPrimitiveType(type)) { if (isPrimitiveType(type)) {
checkPrimitive(errors, ei.path, type, ei.definition, ei.element, profile); checkPrimitive(appContext, errors, ei.path, type, ei.definition, ei.element, profile);
} else { } else {
if (type.equals("Identifier")) if (type.equals("Identifier"))
checkIdentifier(errors, ei.path, ei.element, ei.definition); checkIdentifier(errors, ei.path, ei.element, ei.definition);
@ -3631,8 +3674,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return noBindingMsgSuppressed; return noBindingMsgSuppressed;
} }
public void setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) { public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
this.noBindingMsgSuppressed = noBindingMsgSuppressed; this.noBindingMsgSuppressed = noBindingMsgSuppressed;
return this;
} }
@ -3640,8 +3684,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return noTerminologyChecks; return noTerminologyChecks;
} }
public void setNoTerminologyChecks(boolean noTerminologyChecks) { public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) {
this.noTerminologyChecks = noTerminologyChecks; this.noTerminologyChecks = noTerminologyChecks;
return this;
} }
public void checkAllInvariants(){ public void checkAllInvariants(){
@ -3666,4 +3711,5 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
} }
} }

View File

@ -0,0 +1,23 @@
package org.hl7.fhir.dstu3.validation;
import org.hl7.fhir.dstu3.context.IWorkerContext;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
/**
* Placeholder class - Do not use
*/
class ValidationEngine {
private ValidationEngine() {
}
public IWorkerContext getContext() {
return null;
}
public FHIRPathEngine getFpe() {
return null;
}
}

View File

@ -37,6 +37,7 @@ import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.CodeType; import org.hl7.fhir.dstu3.model.CodeType;
import org.hl7.fhir.dstu3.model.ContactPoint; import org.hl7.fhir.dstu3.model.ContactPoint;
import org.hl7.fhir.dstu3.model.Extension;
import org.hl7.fhir.dstu3.model.Observation; import org.hl7.fhir.dstu3.model.Observation;
import org.hl7.fhir.dstu3.model.Observation.ObservationStatus; import org.hl7.fhir.dstu3.model.Observation.ObservationStatus;
import org.hl7.fhir.dstu3.model.Patient; import org.hl7.fhir.dstu3.model.Patient;
@ -96,19 +97,19 @@ public class FhirInstanceValidatorDstu3Test {
ContactPoint t = p.addTelecom(); ContactPoint t = p.addTelecom();
t.setSystem(org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem.URL); t.setSystem(org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem.URL);
t.setValue("http://infoway-inforoute.ca"); t.setValue("http://infoway-inforoute.ca");
ValidationResult results = myVal.validateWithResult(p); ValidationResult results = myVal.validateWithResult(p);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results); List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome, empty()); assertThat(outcome, empty());
} }
/** /**
* See #370 * See #370
*/ */
@Test @Test
public void testValidateRelatedPerson() { public void testValidateRelatedPerson() {
/* /*
* Try with a code that is in http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype * Try with a code that is in http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype
* and therefore should validate * and therefore should validate
@ -116,7 +117,7 @@ public class FhirInstanceValidatorDstu3Test {
RelatedPerson rp = new RelatedPerson(); RelatedPerson rp = new RelatedPerson();
rp.getPatient().setReference("Patient/1"); rp.getPatient().setReference("Patient/1");
rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("c"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("c");
ValidationResult results = myVal.validateWithResult(rp); ValidationResult results = myVal.validateWithResult(rp);
List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results); List<SingleValidationMessage> outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome, empty()); assertThat(outcome, empty());
@ -127,25 +128,24 @@ public class FhirInstanceValidatorDstu3Test {
rp = new RelatedPerson(); rp = new RelatedPerson();
rp.getPatient().setReference("Patient/1"); rp.getPatient().setReference("Patient/1");
rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("C"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("C");
results = myVal.validateWithResult(rp); results = myVal.validateWithResult(rp);
outcome = logResultsAndReturnNonInformationalOnes(results); outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome, empty()); assertThat(outcome, empty());
/* /*
* Now a bad code * Now a bad code
*/ */
rp = new RelatedPerson(); rp = new RelatedPerson();
rp.getPatient().setReference("Patient/1"); rp.getPatient().setReference("Patient/1");
rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("GAGAGAGA"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("GAGAGAGA");
results = myVal.validateWithResult(rp); results = myVal.validateWithResult(rp);
outcome = logResultsAndReturnNonInformationalOnes(results); outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome, not(empty())); assertThat(outcome, not(empty()));
} }
@Test @Test
// @Ignore // @Ignore
public void testValidateBuiltInProfiles() throws Exception { public void testValidateBuiltInProfiles() throws Exception {
@ -163,13 +163,13 @@ public class FhirInstanceValidatorDstu3Test {
ids.add(next.getId()); ids.add(next.getId());
if (next instanceof StructureDefinition) { if (next instanceof StructureDefinition) {
StructureDefinition sd = (StructureDefinition)next; StructureDefinition sd = (StructureDefinition) next;
if (sd.getKind() == StructureDefinitionKind.LOGICAL) { if (sd.getKind() == StructureDefinitionKind.LOGICAL) {
ourLog.info("Skipping logical type: {}", next.getId()); ourLog.info("Skipping logical type: {}", next.getId());
continue; continue;
} }
} }
ourLog.info("Validating {}", next.getId()); ourLog.info("Validating {}", next.getId());
ourLog.trace(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next)); ourLog.trace(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next));
@ -213,11 +213,11 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(1, fpOutput.size()); assertEquals(1, fpOutput.size());
bool = (BooleanType) fpOutput.get(0); bool = (BooleanType) fpOutput.get(0);
assertTrue(bool.getValue()); assertTrue(bool.getValue());
// //
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()"); // fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size()); // assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0); // bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue()); // assertTrue(bool.getValue());
ValidationResult output = myVal.validateWithResult(inputString); ValidationResult output = myVal.validateWithResult(inputString);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output); List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
@ -337,7 +337,8 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0; int index = 0;
for (SingleValidationMessage next : theOutput.getMessages()) { for (SingleValidationMessage next : theOutput.getMessages()) {
ourLog.info("Result {}: {} - {}:{} {} - {}", new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() }); ourLog.info("Result {}: {} - {}:{} {} - {}",
new Object[] { index, next.getSeverity(), defaultString(next.getLocationLine()), defaultString(next.getLocationCol()), next.getLocationString(), next.getMessage() });
index++; index++;
retVal.add(next); retVal.add(next);
@ -404,6 +405,73 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(output.toString(), 0, output.getMessages().size()); assertEquals(output.toString(), 0, output.getMessages().size());
} }
@Test
public void testValidateRawJsonResourceWithUnknownExtension() {
Patient patient = new Patient();
patient.setId("1");
Extension ext = patient.addExtension();
ext.setUrl("http://hl7.org/fhir/v3/ethnicity");
ext.setValue(new CodeType("Hispanic or Latino"));
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
/*
* {
* "resourceType": "Patient",
* "id": "1",
* "extension": [
* {
* "url": "http://hl7.org/fhir/v3/ethnicity",
* "valueCode": "Hispanic or Latino"
* }
* ]
* }
*/
ValidationResult output = myVal.validateWithResult(encoded);
assertEquals(output.toString(), 1, output.getMessages().size());
assertEquals("Unknown extension http://hl7.org/fhir/v3/ethnicity", output.getMessages().get(0).getMessage());
assertEquals(ResultSeverityEnum.INFORMATION, output.getMessages().get(0).getSeverity());
}
@Test
public void testValidateRawJsonResourceWithUnknownExtensionNotAllowed() {
Patient patient = new Patient();
patient.setId("1");
Extension ext = patient.addExtension();
ext.setUrl("http://hl7.org/fhir/v3/ethnicity");
ext.setValue(new CodeType("Hispanic or Latino"));
String encoded = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
/*
* {
* "resourceType": "Patient",
* "id": "1",
* "extension": [
* {
* "url": "http://hl7.org/fhir/v3/ethnicity",
* "valueCode": "Hispanic or Latino"
* }
* ]
* }
*/
myInstanceVal.setAnyExtensionsAllowed(false);
ValidationResult output = myVal.validateWithResult(encoded);
assertEquals(output.toString(), 1, output.getMessages().size());
assertEquals("The extension http://hl7.org/fhir/v3/ethnicity is unknown, and not allowed here", output.getMessages().get(0).getMessage());
assertEquals(ResultSeverityEnum.ERROR, output.getMessages().get(0).getSeverity());
}
@Test @Test
public void testValidateRawXmlWithMissingRootNamespace() { public void testValidateRawXmlWithMissingRootNamespace() {
//@formatter:off //@formatter:off
@ -422,13 +490,13 @@ public class FhirInstanceValidatorDstu3Test {
+ " <birthDate value=\"1974-12-25\"/>" + " <birthDate value=\"1974-12-25\"/>"
+ "</Patient>"; + "</Patient>";
//@formatter:on //@formatter:on
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 1, output.getMessages().size()); assertEquals(output.toString(), 1, output.getMessages().size());
assertEquals("This cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); assertEquals("This cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
ourLog.info(output.getMessages().get(0).getLocationString()); ourLog.info(output.getMessages().get(0).getLocationString());
} }
@Test @Test
public void testValidateRawJsonResourceBadAttributes() { public void testValidateRawJsonResourceBadAttributes() {
//@formatter:off //@formatter:off
@ -482,7 +550,7 @@ public class FhirInstanceValidatorDstu3Test {
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 2, output.getMessages().size()); assertEquals(output.toString(), 2, output.getMessages().size());
assertThat(output.getMessages().get(0).getMessage(), containsString("Element must have some content")); assertThat(output.getMessages().get(0).getMessage(), containsString("Element must have some content"));
assertThat(output.getMessages().get(1).getMessage(), containsString("primitive types must have a value or must have child extensions")); assertThat(output.getMessages().get(1).getMessage(), containsString("primitive types must have a value or must have child extensions"));
} }
@Test @Test
@ -629,7 +697,9 @@ public class FhirInstanceValidatorDstu3Test {
//@formatter:on //@formatter:on
ValidationResult output = myVal.validateWithResult(input); ValidationResult output = myVal.validateWithResult(input);
logResultsAndReturnAll(output); logResultsAndReturnAll(output);
assertEquals("The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code[notvalidcode] in system[null])", output.getMessages().get(0).getMessage()); assertEquals(
"The value provided ('notvalidcode') is not in the value set http://hl7.org/fhir/ValueSet/observation-status (http://hl7.org/fhir/ValueSet/observation-status, and a code is required from this value set) (error message = Unknown code[notvalidcode] in system[null])",
output.getMessages().get(0).getMessage());
} }
@Test @Test
@ -711,8 +781,7 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> errors = logResultsAndReturnAll(output); List<SingleValidationMessage> errors = logResultsAndReturnAll(output);
assertThat(errors.toString(), errors.size(), greaterThan(0)); assertThat(errors.toString(), errors.size(), greaterThan(0));
assertEquals("Unknown code: http://acme.org / 9988877", errors.get(0).getMessage()); assertEquals("Unknown code: http://acme.org / 9988877", errors.get(0).getMessage());
} }
@Test @Test
@ -725,7 +794,9 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> all = logResultsAndReturnAll(output); List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size()); assertEquals(1, all.size());
assertEquals("Patient.identifier.type", all.get(0).getLocationString()); assertEquals("Patient.identifier.type", all.get(0).getLocationString());
assertEquals("None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code) (codes = http://example.com/foo/bar#bar)", all.get(0).getMessage()); assertEquals(
"None of the codes provided are in the value set http://hl7.org/fhir/ValueSet/identifier-type (http://hl7.org/fhir/ValueSet/identifier-type, and a code should come from this value set unless it has no suitable code) (codes = http://example.com/foo/bar#bar)",
all.get(0).getMessage());
assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity()); assertEquals(ResultSeverityEnum.WARNING, all.get(0).getSeverity());
} }

View File

@ -451,6 +451,11 @@
<artifactId>javax.el-api</artifactId> <artifactId>javax.el-api</artifactId>
<version>3.0.0</version> <version>3.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
<version>1.2</version>
</dependency>
<dependency> <dependency>
<groupId>javax.json</groupId> <groupId>javax.json</groupId>
<artifactId>javax.json-api</artifactId> <artifactId>javax.json-api</artifactId>

View File

@ -58,6 +58,11 @@
(e.g. requests for the second page of results for a search operation). (e.g. requests for the second page of results for a search operation).
Thanks to Eeva Turkka for reporting! Thanks to Eeva Turkka for reporting!
</action> </action>
<action type="add">
Add configuration property to DSTU3 FhirInstanceValidator to
allow client code to change unknown extension handling behaviour.
</action>
</release>
<release version="2.4" date="2017-04-19"> <release version="2.4" date="2017-04-19">
<action type="add"> <action type="add">
This release brings the DSTU3 structures up to FHIR R3 (FHIR 3.0.1) definitions. Note that This release brings the DSTU3 structures up to FHIR R3 (FHIR 3.0.1) definitions. Note that

View File

@ -192,11 +192,10 @@
<section name="Resource Validation (Profile/StructureDefinition)"> <section name="Resource Validation (Profile/StructureDefinition)">
<p> <p>
As of HAPI FHIR 1.2, HAPI supports validation against StructureDefinition HAPI also supports validation against StructureDefinition
resources. This functionality uses the HL7 "InstanceValidator", which is able resources. This functionality uses the HL7 "InstanceValidator", which is able
to to check a resource for conformance to FHIR profiles
check a resource for conformance to a given profile (StructureDefinitions, ValueSets, and CodeSystems),
(StructureDefinitions and ValueSets),
including validating fields, extensions, and codes for conformance to their given ValueSets. including validating fields, extensions, and codes for conformance to their given ValueSets.
</p> </p>
<p> <p>
@ -206,9 +205,9 @@
</p> </p>
<p class="doc_info_bubble"> <p class="doc_info_bubble">
This style of validation is still experimental, and should be used with caution. The instance validator is experimental in the DSTU2 mode, but has become very stable
It is very powerful, but is still under active development and may continue to and full-featured in DSTU3 mode. Use with caution when validating DSTU2 resources using
change over time. instance validator.
</p> </p>
<subsection name="Preparation"> <subsection name="Preparation">
@ -243,7 +242,7 @@
<p> <p>
To execute the validator, you simply create an instance of To execute the validator, you simply create an instance of
<a href="./apidocs-hl7org-dstu2/ca/uhn/fhir/validation/FhirInstanceValidator.html">FhirInstanceValidator</a> <a href="./apidocs-dstu3/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.html">FhirInstanceValidator</a>
and register it to new validator, as shown in the example below. and register it to new validator, as shown in the example below.
</p> </p>