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.filefilter.WildcardFileFilter;
import org.hl7.fhir.instance.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.instance.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.hapi.validation.IValidationSupport;
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.ConceptSetComponent;
import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
import org.hl7.fhir.instance.model.api.IBaseResource;
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.StrictErrorHandler;
import ca.uhn.fhir.rest.client.IGenericClient;
@ -39,7 +41,7 @@ public class ValidatorExamples {
public void validationIntro() {
// START SNIPPET: validationIntro
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Ask the context for a validator
FhirValidator validator = ctx.newValidator();
@ -74,7 +76,7 @@ public class ValidatorExamples {
// Create a context, set the error handler and instruct
// the server to use it
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
ctx.setParserErrorHandler(new StrictErrorHandler());
setFhirContext(ctx);
}
@ -85,19 +87,19 @@ public class ValidatorExamples {
@SuppressWarnings("unused")
public void enableValidation() {
// START SNIPPET: clientValidation
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
ctx.setParserErrorHandler(new StrictErrorHandler());
// 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
}
public void parserValidation() {
// START SNIPPET: parserValidation
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Create a parser and configure it to use the strict error handler
IParser parser = ctx.newXmlParser();
@ -114,13 +116,13 @@ public class ValidatorExamples {
public void validateResource() {
// START SNIPPET: basicValidation
// As always, you need a context
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Create and populate a new patient object
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.addTelecom().setSystem(ContactPointSystemEnum.PHONE).setValue("416 123-4567");
p.addTelecom().setSystem(ContactPointSystem.PHONE).setValue("416 123-4567");
// Request a validator and apply it
FhirValidator val = ctx.newValidator();
@ -168,20 +170,27 @@ public class ValidatorExamples {
private static void instanceValidator() throws Exception {
// START SNIPPET: instanceValidator
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Create a FhirInstanceValidator and register it to a validator
FhirValidator validator = ctx.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator();
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
* populated, but it is missing Observation.status, which is mandatory.
*/
Observation obs = new Observation();
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
ValidationResult result = validator.validateWithResult(obs);
@ -205,7 +214,7 @@ public class ValidatorExamples {
private static void instanceValidatorCustom() throws Exception {
// START SNIPPET: instanceValidatorCustom
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Create a FhirInstanceValidator and register it to a validator
FhirValidator validator = ctx.newValidator();
@ -214,35 +223,47 @@ public class ValidatorExamples {
IValidationSupport valSupport = new IValidationSupport() {
@Override
public CodeValidationResult validateCode(FhirContext theContext, String theCodeSystem, String theCode, String theDisplay) {
// TODO: Implement
return null;
}
@Override
public org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent expandValueSet(FhirContext theContext, org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent theInclude) {
// TODO: implement
return null;
}
@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
// TODO: Implement
return false;
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
// TODO: implement
return null;
}
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
// TODO: Implement
return null;
}
@Override
public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) {
// TODO: implement
return null;
}
@Override
public ValueSet 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 ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) {
// 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")
private static void validateFiles() throws Exception {
// START SNIPPET: validateFiles
FhirContext ctx = FhirContext.forDstu2();
FhirContext ctx = FhirContext.forDstu3();
// Create a validator and configure it
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>
</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 -->
<dependency>

View File

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

View File

@ -163,7 +163,6 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
* @param theId
* @param theRequestDetails
* TODO
* @return
* @throws ResourceNotFoundException
* 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 {
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 DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
/**
* Constructor
*
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
}
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
/**
* Constructor which uses the given validation support
*
* @param theValidationSupport
* The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
/**
* Constructor
*
* Uses {@link DefaultProfileValidationSupport} for {@link IValidationSupport validation support}
*/
public FhirInstanceValidator() {
this(new DefaultProfileValidationSupport());
}
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();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
private String determineResourceName(Document theDocument) {
Element root = null;
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <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>
*
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)}
*/
public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel;
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
/**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <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>
*
* @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
* 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;
}
/**
* Returns the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public IValidationSupport getValidationSupport() {
return myValidationSupport;
}
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
* {@link DefaultProfileValidationSupport} if the no-arguments constructor for this object was used.
*/
public void setValidationSupport(IValidationSupport theValidationSupport) {
myValidationSupport = theValidationSupport;
}
/**
* 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 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;
IEvaluationContext evaluationCtx = new NullEvaluationContext();
try {
v = new InstanceValidator(workerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
}
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(true);
v.setResourceIdRule(IdStatus.OPTIONAL);
/**
* Sets the {@link IValidationSupport validation support} in use by this validator. Default is an instance of
* {@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) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} 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);
}
InstanceValidator v;
IEvaluationContext evaluationCtx = new NullEvaluationContext();
try {
v = new InstanceValidator(workerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
String resourceName = determineResourceName(document);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
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);
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
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);
}
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
if ("Binding has no source, so can't be checked".equals(next.getMessage())) {
messages.remove(i);
i--;
}
}
return messages;
}
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} 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);
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
String resourceName = determineResourceName(document);
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
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
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException {
return null;
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
if ("Binding has no source, so can't be checked".equals(next.getMessage())) {
messages.remove(i);
i--;
}
}
return messages;
}
@Override
public List<Base> executeFunction(Object theAppContext, String theFunctionName, List<List<Base>> theParameters) {
return null;
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
@Override
public boolean log(String theArgument, List<Base> theFocus) {
return false;
}
public class NullEvaluationContext implements IEvaluationContext {
@Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException {
return null;
}
@Override
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException {
return null;
}
@Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException {
return null;
}
@Override
public List<Base> executeFunction(Object theAppContext, String theFunctionName, List<List<Base>> theParameters) {
return null;
}
@Override
public FunctionDetails resolveFunction(String theFunctionName) {
return null;
}
@Override
public boolean log(String theArgument, List<Base> theFocus) {
return false;
}
@Override
public Base resolveReference(Object theAppContext, String theUrl) {
return null;
}
@Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException {
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.Manager.FhirFormat;
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.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
@ -23,8 +24,26 @@ import com.google.gson.JsonObject;
*/
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 {
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 {
@ -47,6 +66,7 @@ public interface IResourceValidator {
}
/**
* how much to check displays for coded elements
* @return
@ -69,20 +89,19 @@ public interface IResourceValidator {
*
*/
BestPracticeWarningLevel getBasePracticeWarningLevel();
void setBestPracticeWarningLevel(BestPracticeWarningLevel value);
IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value);
IValidatorResourceFetcher getFetcher();
void setFetcher(IValidatorResourceFetcher value);
IResourceValidator setFetcher(IValidatorResourceFetcher value);
boolean isNoBindingMsgSuppressed();
void setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed);
IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed);
public boolean isNoInvariantChecks();
public void setNoInvariantChecks(boolean value) ;
public IResourceValidator setNoInvariantChecks(boolean value) ;
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

View File

@ -11,6 +11,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
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.IEvaluationContext;
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.ProfileRegistration;
import org.hl7.fhir.exceptions.DefinitionException;
@ -151,6 +154,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean noBindingMsgSuppressed;
private HashMap<Element, ResourceProfiles> resourceProfilesMap;
private IValidatorResourceFetcher fetcher;
long time = 0;
/*
* Keeps track of whether a particular profile has been checked or not yet
@ -288,22 +292,32 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
source = Source.InstanceValidator;
}
public InstanceValidator(ValidationEngine engine) {
super();
this.context = engine.getContext();
fpe = engine.getFpe();
source = Source.InstanceValidator;
}
@Override
public boolean isNoInvariantChecks() {
return noInvariantChecks;
}
@Override
public void setNoInvariantChecks(boolean value) {
public IResourceValidator setNoInvariantChecks(boolean value) {
this.noInvariantChecks = value;
return this;
}
public IValidatorResourceFetcher getFetcher() {
return this.fetcher;
}
public void setFetcher(IValidatorResourceFetcher value) {
public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
this.fetcher = value;
return this;
}
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");
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 {
if (checkCode(errors, element, path, code, system, display))
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) {
checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus);
checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus);
@ -1156,11 +1180,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
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())) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "primitive types must have a value or must have child extensions");
return;
}
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'");
}
@ -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().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());
// 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 (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0, "@value cannot be empty")) {
@ -1345,55 +1378,78 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return;
}
String refType = ref.startsWith("#")? "contained": (localResolve(ref, stack, errors, path)!=null ? "bundle" : "remote");
Element we = resolve(appContext, ref, stack, errors, path);
Element we = localResolve(ref, stack, errors, path);
String refType;
if (ref.startsWith("#")) {
refType = "contained";
} else {
if (we == null) {
refType = "remote";
} else {
refType = "bundle";
}
}
String ft;
if (we != null)
ft = we.getType();
else
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 (hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) {
boolean ok = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (TypeRefComponent type : container.getType()) {
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)
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
if (pol.checkExists()) {
if (we == null) {
if (fetcher == null)
throw new FHIRException("Resource resolution services not provided");
we = fetcher.fetch(appContext, ref);
}
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, we != null, "Unable to resolve resource '"+ref+"'");
}
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) {
doResourceProfile(appContext, we, pr, errors, stack.push(we, -1, null, null), path, element);
if (we != null && pol.checkType()) {
if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft!=null, "Unable to determine type of target resource")) {
boolean ok = false;
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (TypeRefComponent type : container.getType()) {
if (!ok && type.getCode().equals("Reference")) {
// 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("*")) {
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() + ")");
}
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....
}
}
@ -1571,27 +1627,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return element;
List<String> nodes = new ArrayList<String>();
String s = discriminator;
do {
String node = null;
if (s.contains(".")) {
node = s.substring(0, s.indexOf('.'));
if (node.contains("(")) {
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());
Matcher matcher = Pattern.compile("([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?)(\\.([a-zA-Z0-0]+(\\([^\\(^\\)]*\\))?))*").matcher(discriminator);
while (matcher.find()) {
if (!matcher.group(1).startsWith("@"))
nodes.add(matcher.group(1));
if (matcher.groupCount()>4 && matcher.group(4)!= null && !matcher.group(4).startsWith("@"))
nodes.add(matcher.group(4));
}
for (String fullnode : nodes) {
String node = fullnode.contains("(") ? fullnode.substring(0, fullnode.indexOf('(')) : fullnode;
@ -2015,7 +2057,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
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);
if (local!=null)
return local;
@ -2149,8 +2191,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
this.anyExtensionsAllowed = anyExtensionsAllowed;
}
public void setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
public IResourceValidator setBestPracticeWarningLevel(BestPracticeWarningLevel value) {
bpWarnings = value;
return this;
}
@Override
@ -2197,8 +2240,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* @throws IOException
* @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 {
if (!slice.getSlicing().hasDiscriminator())
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 (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case
ExpressionNode n = (ExpressionNode) ed.getUserData("slice.expression.cache");
@ -2206,7 +2249,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime();
// GG: this approach is flawed because it treats discriminators individually rather than collectively
String expression = "true";
for (ElementDefinitionSlicingDiscriminatorComponent s : slice.getSlicing().getDiscriminator()) {
for (ElementDefinitionSlicingDiscriminatorComponent s : slicer.getSlicing().getDiscriminator()) {
String discriminator = s.getPath();
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);
@ -2226,7 +2269,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
type = criteriaElement.getType().get(0).getCode();
}
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())
expression = expression + " and this is " + type;
else
@ -2249,7 +2293,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (fixed instanceof BooleanType) {
expression = expression + ((BooleanType)fixed).asStringValue();
} 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) {
expression = expression + " and " + discriminator + " in '" + criteriaElement.getBinding().getValueSetReference().getReference() + "'";
} 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")) {
expression = expression + " and " + discriminator + ".exists().not()";
} 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;
// firstBase = ebase == null ? base : ebase;
long time = 0;
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.markValidation(profile, definition);
@ -2932,7 +2975,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// 2. assign children to a definition
// for each definition, for each child, check whether it belongs in the slice
ElementDefinition slice = null;
ElementDefinition slicer = null;
boolean unsupportedSlicing = false;
List<String> problematicPaths = new ArrayList<String>();
String slicingPath = null;
@ -2949,26 +2992,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
slicingPath = null;
// where are we with slicing
if (ed.hasSlicing()) {
if (slice != null && slice.getPath().equals(ed.getPath()))
throw new DefinitionException("Slice encountered midway through path on " + slice.getPath());
slice = ed;
if (slicer != null && slicer.getPath().equals(ed.getPath()))
throw new DefinitionException("Slice encountered midway through path on " + slicer.getPath());
slicer = ed;
process = false;
sliceOffset = i;
} else if (slice != null && !slice.getPath().equals(ed.getPath()))
slice = null;
} else if (slicer != null && !slicer.getPath().equals(ed.getPath()))
slicer = null;
// if (process) {
for (ElementInfo ei : children) {
boolean match = false;
if (slice == null || slice == ed) {
if (slicer == null || slicer == ed) {
match = nameMatches(ei.name, tail(ed.getPath()));
} else {
// ei.slice = slice;
if (nameMatches(ei.name, tail(ed.getPath())))
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)
ei.slice = slice;
ei.slice = slicer;
} catch (FHIRException e) {
warning(errors, IssueType.PROCESSING, ei.line(), ei.col(), ei.path, false, e.getMessage());
unsupportedSlicing = true;
@ -2976,7 +3019,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
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;
if (ei.slice == null) {
ei.index = i;
@ -2995,8 +3038,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
int lastSlice = -1;
for (ElementInfo ei : children) {
String sliceInfo = "";
if (slice != null)
sliceInfo = " (slice: " + slice.getPath()+")";
if (slicer != null)
sliceInfo = " (slice: " + slicer.getPath()+")";
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()));
else if (!unsupportedSlicing)
@ -3131,7 +3174,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (type != null) {
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 {
if (type.equals("Identifier"))
checkIdentifier(errors, ei.path, ei.element, ei.definition);
@ -3631,8 +3674,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return noBindingMsgSuppressed;
}
public void setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
public IResourceValidator setNoBindingMsgSuppressed(boolean noBindingMsgSuppressed) {
this.noBindingMsgSuppressed = noBindingMsgSuppressed;
return this;
}
@ -3640,8 +3684,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return noTerminologyChecks;
}
public void setNoTerminologyChecks(boolean noTerminologyChecks) {
public IResourceValidator setNoTerminologyChecks(boolean noTerminologyChecks) {
this.noTerminologyChecks = noTerminologyChecks;
return this;
}
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.CodeType;
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.ObservationStatus;
import org.hl7.fhir.dstu3.model.Patient;
@ -132,7 +133,6 @@ public class FhirInstanceValidatorDstu3Test {
outcome = logResultsAndReturnNonInformationalOnes(results);
assertThat(outcome, empty());
/*
* Now a bad code
*/
@ -163,7 +163,7 @@ public class FhirInstanceValidatorDstu3Test {
ids.add(next.getId());
if (next instanceof StructureDefinition) {
StructureDefinition sd = (StructureDefinition)next;
StructureDefinition sd = (StructureDefinition) next;
if (sd.getKind() == StructureDefinitionKind.LOGICAL) {
ourLog.info("Skipping logical type: {}", next.getId());
continue;
@ -214,10 +214,10 @@ public class FhirInstanceValidatorDstu3Test {
bool = (BooleanType) fpOutput.get(0);
assertTrue(bool.getValue());
//
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue());
// fpOutput = fp.evaluate(bundle, "component.where(code = %resource.code).empty()");
// assertEquals(1, fpOutput.size());
// bool = (BooleanType) fpOutput.get(0);
// assertTrue(bool.getValue());
ValidationResult output = myVal.validateWithResult(inputString);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
@ -337,7 +337,8 @@ public class FhirInstanceValidatorDstu3Test {
int index = 0;
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++;
retVal.add(next);
@ -404,6 +405,73 @@ public class FhirInstanceValidatorDstu3Test {
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
public void testValidateRawXmlWithMissingRootNamespace() {
//@formatter:off
@ -482,7 +550,7 @@ public class FhirInstanceValidatorDstu3Test {
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 2, output.getMessages().size());
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
@ -629,7 +697,9 @@ public class FhirInstanceValidatorDstu3Test {
//@formatter:on
ValidationResult output = myVal.validateWithResult(input);
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
@ -712,7 +782,6 @@ public class FhirInstanceValidatorDstu3Test {
assertThat(errors.toString(), errors.size(), greaterThan(0));
assertEquals("Unknown code: http://acme.org / 9988877", errors.get(0).getMessage());
}
@Test
@ -725,7 +794,9 @@ public class FhirInstanceValidatorDstu3Test {
List<SingleValidationMessage> all = logResultsAndReturnAll(output);
assertEquals(1, all.size());
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());
}

View File

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

View File

@ -58,6 +58,11 @@
(e.g. requests for the second page of results for a search operation).
Thanks to Eeva Turkka for reporting!
</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">
<action type="add">
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)">
<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
to
check a resource for conformance to a given profile
(StructureDefinitions and ValueSets),
to check a resource for conformance to FHIR profiles
(StructureDefinitions, ValueSets, and CodeSystems),
including validating fields, extensions, and codes for conformance to their given ValueSets.
</p>
<p>
@ -206,9 +205,9 @@
</p>
<p class="doc_info_bubble">
This style of validation is still experimental, and should be used with caution.
It is very powerful, but is still under active development and may continue to
change over time.
The instance validator is experimental in the DSTU2 mode, but has become very stable
and full-featured in DSTU3 mode. Use with caution when validating DSTU2 resources using
instance validator.
</p>
<subsection name="Preparation">
@ -243,7 +242,7 @@
<p>
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.
</p>