diff --git a/examples/src/main/java/example/ValidatorExamples.java b/examples/src/main/java/example/ValidatorExamples.java index c5b181a887e..e9ce3c9204a 100644 --- a/examples/src/main/java/example/ValidatorExamples.java +++ b/examples/src/main/java/example/ValidatorExamples.java @@ -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(); @@ -213,36 +222,48 @@ public class ValidatorExamples { validator.registerValidatorModule(instanceValidator); 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 fetchResource(FhirContext theContext, Class theClass, String theUri) { - // TODO: Implement - return null; - } - - @Override - public ValueSet fetchCodeSystem(FhirContext theContext, String theSystem) { - // TODO: Implement - return null; - } - @Override - public ValueSetExpansionComponent expandValueSet(FhirContext theContext, ConceptSetComponent theInclude) { - // 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 List fetchAllStructureDefinitions(FhirContext theContext) { + // TODO: implement + return null; + } + + @Override + public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { + // TODO: implement + return null; + } + + @Override + public T fetchResource(FhirContext theContext, Class 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") 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(); diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml index c513b7a6de6..d347430a11f 100644 --- a/hapi-fhir-jpaserver-base/pom.xml +++ b/hapi-fhir-jpaserver-base/pom.xml @@ -130,6 +130,16 @@ junit junit jdom org.jdom gson com.google.code.gson --> + + + javax.interceptor + javax.interceptor-api + provided + + + diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java index 14a5fcbfe85..3aa1773f9f6 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirDao.java @@ -1083,8 +1083,8 @@ public abstract class BaseHapiFhirDao 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 diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java index ffad55331b7..bca730aec07 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/IFhirResourceDao.java @@ -163,7 +163,6 @@ public interface IFhirResourceDao extends IDao { * @param theId * @param theRequestDetails * TODO - * @return * @throws ResourceNotFoundException * If the ID is not known to the server */ diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java index 6aad481c4fa..91154b6773f 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java @@ -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}). - *

- * 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. - *

- * - * @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}). + *

+ * 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. + *

+ * + * @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. - *

- * 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. - *

- * - * @param theBestPracticeWarningLevel - * The level, must not be null - */ - 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 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. + *

+ * 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. + *

+ * + * @param theBestPracticeWarningLevel + * The level, must not be null + */ + 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 messages = new ArrayList(); + protected List 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 messages = new ArrayList(); - 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 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 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 executeFunction(Object theAppContext, String theFunctionName, List> theParameters) { - return null; - } + @Override + protected List validate(IValidationContext theCtx) { + return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding()); + } - @Override - public boolean log(String theArgument, List 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 theParameters) throws PathEngineException { + return null; + } - @Override - public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException { - return null; - } + @Override + public List executeFunction(Object theAppContext, String theFunctionName, List> theParameters) { + return null; + } - @Override - public FunctionDetails resolveFunction(String theFunctionName) { - return null; - } + @Override + public boolean log(String theArgument, List 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; + } + + } } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/IResourceValidator.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/IResourceValidator.java index 6a01334b7c7..e3a578aa4c4 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/IResourceValidator.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/utils/IResourceValidator.java @@ -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 { @@ -46,6 +65,7 @@ public interface IResourceValidator { OPTIONAL, REQUIRED, PROHIBITED } + /** * how much to check displays for coded elements @@ -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 diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java index b88847744d2..84846261ba9 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java @@ -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 resourceProfilesMap; private IValidatorResourceFetcher fetcher; + long time = 0; /* * 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); 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 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,12 +1180,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus); } - private void checkPrimitive(List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile) throws FHIRException { + private void checkPrimitive(Object appContext, List 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; } - 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'"); } 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().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,58 +1378,81 @@ 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 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 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.... } } - + private void doResourceProfile(Object appContext, Element resource, String profile, List errors, NodeStack stack, String path, Element element) throws FHIRException, IOException { ResourceProfiles resourceProfiles = addResourceProfile(errors, resource, profile, path, element, stack); if (resourceProfiles.isProcessed()) { @@ -1571,27 +1627,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return element; List nodes = new ArrayList(); - 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 errors, String path) throws FHIRFormatError, DefinitionException, IOException { + private Element resolve(Object appContext, String ref, NodeStack stack, List 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 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 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 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 problematicPaths = new ArrayList(); 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 } } } + } diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/ValidationEngine.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/ValidationEngine.java new file mode 100644 index 00000000000..d3106463952 --- /dev/null +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/validation/ValidationEngine.java @@ -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; + } + +} diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java index 079806de8da..2384421dd4f 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/validation/FhirInstanceValidatorDstu3Test.java @@ -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; @@ -96,19 +97,19 @@ public class FhirInstanceValidatorDstu3Test { ContactPoint t = p.addTelecom(); t.setSystem(org.hl7.fhir.dstu3.model.ContactPoint.ContactPointSystem.URL); t.setValue("http://infoway-inforoute.ca"); - + ValidationResult results = myVal.validateWithResult(p); List outcome = logResultsAndReturnNonInformationalOnes(results); assertThat(outcome, empty()); - + } - + /** * See #370 */ @Test public void testValidateRelatedPerson() { - + /* * Try with a code that is in http://hl7.org/fhir/ValueSet/relatedperson-relationshiptype * and therefore should validate @@ -116,7 +117,7 @@ public class FhirInstanceValidatorDstu3Test { RelatedPerson rp = new RelatedPerson(); rp.getPatient().setReference("Patient/1"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("c"); - + ValidationResult results = myVal.validateWithResult(rp); List outcome = logResultsAndReturnNonInformationalOnes(results); assertThat(outcome, empty()); @@ -127,25 +128,24 @@ public class FhirInstanceValidatorDstu3Test { rp = new RelatedPerson(); rp.getPatient().setReference("Patient/1"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("C"); - + results = myVal.validateWithResult(rp); outcome = logResultsAndReturnNonInformationalOnes(results); assertThat(outcome, empty()); - /* * Now a bad code */ rp = new RelatedPerson(); rp.getPatient().setReference("Patient/1"); rp.getRelationship().addCoding().setSystem("http://hl7.org/fhir/v2/0131").setCode("GAGAGAGA"); - + results = myVal.validateWithResult(rp); outcome = logResultsAndReturnNonInformationalOnes(results); assertThat(outcome, not(empty())); } - + @Test // @Ignore public void testValidateBuiltInProfiles() throws Exception { @@ -163,13 +163,13 @@ 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; } } - + ourLog.info("Validating {}", next.getId()); ourLog.trace(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(next)); @@ -213,11 +213,11 @@ public class FhirInstanceValidatorDstu3Test { 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()); + // + // 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 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 @@ -422,13 +490,13 @@ public class FhirInstanceValidatorDstu3Test { + " " + ""; //@formatter:on - + ValidationResult output = myVal.validateWithResult(input); assertEquals(output.toString(), 1, output.getMessages().size()); assertEquals("This cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage()); ourLog.info(output.getMessages().get(0).getLocationString()); } - + @Test public void testValidateRawJsonResourceBadAttributes() { //@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 @@ -711,8 +781,7 @@ public class FhirInstanceValidatorDstu3Test { List errors = logResultsAndReturnAll(output); 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 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()); } diff --git a/pom.xml b/pom.xml index 47b63b3a1ab..5e1d82b8ad8 100644 --- a/pom.xml +++ b/pom.xml @@ -451,6 +451,11 @@ javax.el-api 3.0.0
+ + javax.interceptor + javax.interceptor-api + 1.2 + javax.json javax.json-api diff --git a/src/changes/changes.xml b/src/changes/changes.xml index d3d31d5d6d4..b73c100478a 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -58,6 +58,11 @@ (e.g. requests for the second page of results for a search operation). Thanks to Eeva Turkka for reporting! + + Add configuration property to DSTU3 FhirInstanceValidator to + allow client code to change unknown extension handling behaviour. + + This release brings the DSTU3 structures up to FHIR R3 (FHIR 3.0.1) definitions. Note that diff --git a/src/site/xdoc/doc_validation.xml b/src/site/xdoc/doc_validation.xml index 67524c7c656..25b4d58fae8 100644 --- a/src/site/xdoc/doc_validation.xml +++ b/src/site/xdoc/doc_validation.xml @@ -192,11 +192,10 @@

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

@@ -206,9 +205,9 @@

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

@@ -243,7 +242,7 @@

To execute the validator, you simply create an instance of - FhirInstanceValidator + FhirInstanceValidator and register it to new validator, as shown in the example below.