diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/instancevalidator/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/instancevalidator/InstanceValidator.java
index eb162d7b5..490c4d592 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/instancevalidator/InstanceValidator.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/instancevalidator/InstanceValidator.java
@@ -32,7 +32,9 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
+import java.util.ResourceBundle;
import java.util.Set;
import java.util.UUID;
@@ -167,5421 +169,5366 @@ import ca.uhn.fhir.util.ObjectUtil;
public class InstanceValidator extends BaseValidator implements IResourceValidator {
- private class ValidatorHostServices implements IEvaluationContext {
-
- @Override
- public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
- ValidatorHostContext c = (ValidatorHostContext) appContext;
- if (externalHostServices != null)
- return externalHostServices.resolveConstant(c.getAppContext(), name, beforeContext);
- else
- return null;
- }
-
- @Override
- public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
- ValidatorHostContext c = (ValidatorHostContext) appContext;
- if (externalHostServices != null)
- return externalHostServices.resolveConstantType(c.getAppContext(), name);
- else
- return null;
- }
-
- @Override
- public boolean log(String argument, List focus) {
- if (externalHostServices != null)
- return externalHostServices.log(argument, focus);
- else
- return false;
- }
-
- @Override
- public FunctionDetails resolveFunction(String functionName) {
- throw new Error("Not done yet (ValidatorHostServices.resolveFunction): " + functionName);
- }
-
- @Override
- public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException {
- throw new Error("Not done yet (ValidatorHostServices.checkFunction)");
- }
-
- @Override
- public List executeFunction(Object appContext, String functionName, List> parameters) {
- throw new Error("Not done yet (ValidatorHostServices.executeFunction)");
- }
-
- @Override
- public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
- ValidatorHostContext c = (ValidatorHostContext) appContext;
-
- if (refContext != null && refContext.hasUserData("validator.bundle.resolution")) {
- return (Base) refContext.getUserData("validator.bundle.resolution");
- }
-
- if (c.getAppContext() instanceof Element) {
- Element bnd = (Element) c.getAppContext();
- Base res = resolveInBundle(url, bnd);
- if (res != null)
- return res;
- }
- Base res = resolveInBundle(url, c.getResource());
- if (res != null)
- return res;
- res = resolveInBundle(url, c.getContainer());
- if (res != null)
- return res;
-
- if (externalHostServices != null)
- return externalHostServices.resolveReference(c.getAppContext(), url, refContext);
- else if (fetcher != null)
- try {
- return fetcher.fetch(c.getAppContext(), url);
- } catch (IOException e) {
- throw new FHIRException(e);
- }
- else
- throw new Error("Not done yet - resolve " + url + " locally (2)");
-
- }
-
- public Base resolveInBundle(String url, Element bnd) {
- if (bnd == null)
- return null;
- if (bnd.fhirType().equals("Bundle")) {
- for (Element be : bnd.getChildrenByName("entry")) {
- Element res = be.getNamedChild("resource");
- if (res != null) {
- String fullUrl = be.getChildValue("fullUrl");
- String rt = res.fhirType();
- String id = res.getChildValue("id");
- if (url.equals(fullUrl))
- return res;
- if (url.equals(rt + "/" + id))
- return res;
- }
- }
- }
- return null;
- }
-
- @Override
- public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
- ValidatorHostContext ctxt = (ValidatorHostContext) appContext;
- StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
- if (sd == null) {
- throw new FHIRException("Unable to resolve " + url);
- }
- InstanceValidator self = InstanceValidator.this;
- List valerrors = new ArrayList();
- if (item instanceof Resource) {
- try {
- Element e = new ObjectConverter(context).convert((Resource) item);
- self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(e));
- } catch (IOException e1) {
- throw new FHIRException(e1);
- }
- } else if (item instanceof Element) {
- Element e = (Element) item;
- if (e.isResource()) {
- self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(e));
- } else {
- throw new FHIRException("Not supported yet");
- }
- } else
- throw new NotImplementedException("Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element");
- boolean ok = true;
- List record = new ArrayList<>();
- for (ValidationMessage v : valerrors) {
- ok = ok && !v.getLevel().isError();
- if (v.getLevel().isError() || v.isSlicingHint()) {
- record.add(v);
- }
- }
- if (!ok && !record.isEmpty()) {
- ctxt.sliceNotes(url, record);
- }
- return ok;
- }
-
- @Override
- public ValueSet resolveValueSet(Object appContext, String url) {
- ValidatorHostContext c = (ValidatorHostContext) appContext;
- if (c.getProfile() != null && url.startsWith("#")) {
- for (Resource r : c.getProfile().getContained()) {
- if (r.getId().equals(url.substring(1))) {
- if (r instanceof ValueSet)
- return (ValueSet) r;
- else
- throw new FHIRException("Reference " + url + " refers to a " + r.fhirType() + " not a ValueSet");
- }
- }
- return null;
- }
- return context.fetchResource(ValueSet.class, url);
- }
-
- }
-
- private IWorkerContext context;
- private FHIRPathEngine fpe;
-
- // configuration items
- private CheckDisplayOption checkDisplay;
- private boolean anyExtensionsAllowed;
- private boolean errorForUnknownProfiles;
- private boolean noInvariantChecks;
- private boolean noTerminologyChecks;
- private boolean hintAboutNonMustSupport;
- private boolean showMessagesFromReferences;
- private BestPracticeWarningLevel bpWarnings;
- private String validationLanguage;
- private boolean baseOnly;
-
- private List extensionDomains = new ArrayList();
-
- private IdStatus resourceIdRule;
- private boolean allowXsiLocation;
-
- // used during the build process to keep the overall volume of messages down
- private boolean suppressLoincSnomedMessages;
-
- // time tracking
- private long overall = 0;
- private long txTime = 0;
- private long sdTime = 0;
- private long loadTime = 0;
- private long fpeTime = 0;
-
- private boolean noBindingMsgSuppressed;
- private boolean debug;
- private Map fetchCache = new HashMap<>();
- private HashMap resourceTracker = new HashMap<>();
- private IValidatorResourceFetcher fetcher;
- long time = 0;
- private IEvaluationContext externalHostServices;
- private boolean noExtensibleWarnings;
- private String serverBase;
-
- private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
- private String executionId;
- private XVerExtensionManager xverManager;
- private IValidationProfileUsageTracker tracker;
- private ValidatorHostServices validatorServices;
- private boolean assumeValidRestReferences;
- private boolean allowExamples;
-
- public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
- super();
- this.context = theContext;
- this.externalHostServices = hostServices;
- fpe = new FHIRPathEngine(context);
- validatorServices = new ValidatorHostServices();
- fpe.setHostServices(validatorServices);
- if (theContext.getVersion().startsWith("3.0") || theContext.getVersion().startsWith("1.0"))
- fpe.setLegacyMode(true);
- source = Source.InstanceValidator;
- }
+ private class ValidatorHostServices implements IEvaluationContext {
@Override
- public boolean isNoExtensibleWarnings() {
- return noExtensibleWarnings;
- }
-
- @Override
- public IResourceValidator setNoExtensibleWarnings(boolean noExtensibleWarnings) {
- this.noExtensibleWarnings = noExtensibleWarnings;
- return this;
- }
-
- @Override
- public boolean isShowMessagesFromReferences() {
- return showMessagesFromReferences;
- }
-
- @Override
- public void setShowMessagesFromReferences(boolean showMessagesFromReferences) {
- this.showMessagesFromReferences = showMessagesFromReferences;
- }
-
- @Override
- public boolean isNoInvariantChecks() {
- return noInvariantChecks;
- }
-
- @Override
- public IResourceValidator setNoInvariantChecks(boolean value) {
- this.noInvariantChecks = value;
- return this;
- }
-
- public IValidatorResourceFetcher getFetcher() {
- return this.fetcher;
- }
-
- public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
- this.fetcher = value;
- return this;
- }
-
- public IValidationProfileUsageTracker getTracker() {
- return this.tracker;
- }
-
- public IResourceValidator setTracker(IValidationProfileUsageTracker value) {
- this.tracker = value;
- return this;
- }
-
-
- public boolean isHintAboutNonMustSupport() {
- return hintAboutNonMustSupport;
- }
-
- public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) {
- this.hintAboutNonMustSupport = hintAboutNonMustSupport;
- }
-
- public boolean isAssumeValidRestReferences() {
- return this.assumeValidRestReferences;
- }
-
- public void setAssumeValidRestReferences(boolean value) {
- this.assumeValidRestReferences = value;
- }
-
- public boolean isAllowExamples() {
- return this.allowExamples;
- }
-
- public void setAllowExamples(boolean value) {
- this.allowExamples = value;
- }
-
-
- private boolean allowUnknownExtension(String url) {
- if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression"))
- // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
- return true;
- for (String s : extensionDomains)
- if (url.startsWith(s))
- return true;
- return anyExtensionsAllowed;
- }
-
- private boolean isKnownExtension(String url) {
- // Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with
- if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION))
- return true;
- for (String s : extensionDomains)
- if (url.startsWith(s))
- return true;
- return false;
- }
-
- private void bpCheck(List errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
- if (bpWarnings != null) {
- switch (bpWarnings) {
- case Error:
- rule(errors, invalid, line, col, literalPath, test, message);
- break;
- case Warning:
- warning(errors, invalid, line, col, literalPath, test, message);
- break;
- case Hint:
- hint(errors, invalid, line, col, literalPath, test, message);
- break;
- default: // do nothing
- break;
- }
- }
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format) throws FHIRException {
- return validate(appContext, errors, stream, format, new ArrayList<>());
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- return validate(appContext, errors, stream, format, profiles);
- }
-
- private StructureDefinition getSpecifiedProfile(String profile) {
- StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile);
- if (sd == null) {
- throw new FHIRException("Unable to locate the profile '" + profile + "' in order to validate against it");
- }
- return sd;
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, List profiles) throws FHIRException {
- ParserBase parser = Manager.makeParser(context, format);
- if (parser instanceof XmlParser)
- ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
- parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
- long t = System.nanoTime();
- Element e;
- try {
- e = parser.parse(stream);
- } catch (IOException e1) {
- throw new FHIRException(e1);
- }
- loadTime = System.nanoTime() - t;
- if (e != null)
- validate(appContext, errors, e, profiles);
- return e;
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource) throws FHIRException {
- return validate(appContext, errors, resource, new ArrayList<>());
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- return validate(appContext, errors, resource, profiles);
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, List profiles) throws FHIRException {
- long t = System.nanoTime();
- Element e;
- try {
- e = new ObjectConverter(context).convert(resource);
- } catch (IOException e1) {
- throw new FHIRException(e1);
- }
- loadTime = System.nanoTime() - t;
- validate(appContext, errors, e, profiles);
- return e;
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element) throws FHIRException {
- return validate(appContext, errors, element, new ArrayList<>());
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- return validate(appContext, errors, element, profiles);
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, List profiles) throws FHIRException {
- XmlParser parser = new XmlParser(context);
- parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
- long t = System.nanoTime();
- Element e;
- try {
- e = parser.parse(element);
- } catch (IOException e1) {
- throw new FHIRException(e1);
- }
- loadTime = System.nanoTime() - t;
- if (e != null)
- validate(appContext, errors, e, profiles);
- return e;
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document) throws FHIRException {
- return validate(appContext, errors, document, new ArrayList<>());
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- return validate(appContext, errors, document, profiles);
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, List profiles) throws FHIRException {
- XmlParser parser = new XmlParser(context);
- parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
- long t = System.nanoTime();
- Element e;
- try {
- e = parser.parse(document);
- } catch (IOException e1) {
- throw new FHIRException(e1);
- }
- loadTime = System.nanoTime() - t;
- if (e != null)
- validate(appContext, errors, e, profiles);
- return e;
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object) throws FHIRException {
- return validate(appContext, errors, object, new ArrayList<>());
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- return validate(appContext, errors, object, profiles);
- }
-
- @Override
- public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, List profiles) throws FHIRException {
- JsonParser parser = new JsonParser(context);
- parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
- long t = System.nanoTime();
- Element e = parser.parse(object);
- loadTime = System.nanoTime() - t;
- if (e != null)
- validate(appContext, errors, e, profiles);
- return e;
- }
-
- @Override
- public void validate(Object appContext, List errors, Element element) throws FHIRException {
- validate(appContext, errors, element, new ArrayList<>());
- }
-
- @Override
- public void validate(Object appContext, List errors, Element element, String profile) throws FHIRException {
- ArrayList profiles = new ArrayList<>();
- if (profile != null) {
- profiles.add(getSpecifiedProfile(profile));
- }
- validate(appContext, errors, element, profiles);
- }
-
- @Override
- public void validate(Object appContext, List errors, Element element, List profiles) throws FHIRException {
- // this is the main entry point; all the other public entry points end up here coming here...
- // so the first thing to do is to clear the internal state
- fetchCache.clear();
- fetchCache.put(element.fhirType() + "/" + element.getIdBase(), element);
- resourceTracker.clear();
- executionId = UUID.randomUUID().toString();
- baseOnly = profiles.isEmpty();
-
- long t = System.nanoTime();
- if (profiles == null || profiles.isEmpty()) {
- validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(element));
- } else {
- for (StructureDefinition defn : profiles) {
- validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(element));
- }
- }
- if (hintAboutNonMustSupport) {
- checkElementUsage(errors, element, new NodeStack(element));
- }
- overall = System.nanoTime() - t;
- }
-
- private void checkElementUsage(List errors, Element element, NodeStack stack) {
- String elementUsage = element.getUserString("elementSupported");
- hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage == null || elementUsage.equals("Y"),
- "The element " + element.getName() + " is not marked as 'mustSupport' in the profile " + element.getProperty().getStructure().getUrl() + ". Consider not using the element, or marking the element as must-Support in the profile");
-
- if (element.hasChildren()) {
- String prevName = "";
- int elementCount = 0;
- for (Element ce : element.getChildren()) {
- if (ce.getName().equals(prevName))
- elementCount++;
- else {
- elementCount = 1;
- prevName = ce.getName();
- }
- checkElementUsage(errors, ce, stack.push(ce, elementCount, null, null));
- }
- }
- }
-
- private boolean check(String v1, String v2) {
- return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
- }
-
- private void checkAddress(List errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
- checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
- checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), fixedSource, "city", focus, pattern);
- checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), fixedSource, "state", focus, pattern);
- checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), fixedSource, "country", focus, pattern);
- checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), fixedSource, "postalCode", focus, pattern);
-
- List lines = new ArrayList();
- focus.getNamedChildren("line", lines);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(),
- "Expected " + Integer.toString(fixed.getLine().size()) + " but found " + Integer.toString(lines.size()) + " line elements")) {
- for (int i = 0; i < lines.size(); i++)
- checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), fixedSource, "coding", focus, pattern);
- }
- }
-
- private void checkAttachment(List errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), fixedSource, "contentType", focus, pattern);
- checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), fixedSource, "language", focus, pattern);
- checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern);
- checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), fixedSource, "url", focus, pattern);
- checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), fixedSource, "size", focus, pattern);
- checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern);
- checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern);
- }
-
- // public API
- private boolean checkCode(List errors, Element element, String path, String code, String system, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException {
- long t = System.nanoTime();
- boolean ss = context.supportsSystem(system);
- txTime = txTime + (System.nanoTime() - t);
- if (ss) {
- t = System.nanoTime();
- ValidationResult s = context.validateCode(new ValidationOptions(stack.workingLang), system, code, checkDisplay ? display : null);
- txTime = txTime + (System.nanoTime() - t);
- if (s == null)
- return true;
- if (s.isOk()) {
- if (s.getMessage() != null)
- txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
- return true;
- }
- if (s.getErrorClass() != null && s.getErrorClass().isInfrastructure())
- txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
- else if (s.getSeverity() == IssueSeverity.INFORMATION)
- txHint(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
- else if (s.getSeverity() == IssueSeverity.WARNING)
- txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
- else
- return txRule(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage() + " for '" + system + "#" + code + "'");
- return true;
- } else if (system.startsWith("http://hl7.org/fhir")) {
- if (Utilities.existsInList(system, "http://hl7.org/fhir/sid/icd-10", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/icd-10", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/srt"))
- return true; // else don't check these (for now)
- else if (system.startsWith("http://hl7.org/fhir/test"))
- return true; // we don't validate these
- else {
- CodeSystem cs = getCodeSystem(system);
- if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null, "Unknown Code System '" + system + "'")) {
- ConceptDefinitionComponent def = getCodeDefinition(cs, code);
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null, "Unknown Code (" + system + "#" + code + ")"))
- return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()), "Display should be '" + def.getDisplay() + "'");
- }
- return false;
- }
- } else if (context.isNoTerminologyServer() && Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
- return true; // no checks in this case
- } else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Invalid System URI: " + system);
- return false;
- } else {
- try {
- if (context.fetchResourceWithException(ValueSet.class, system) != null) {
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Invalid System URI: " + system + " - cannot use a value set URI as a system");
- // Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back.
- }
- hint(errors, IssueType.UNKNOWN, element.line(), element.col(), path, false, "Code System URI '" + system + "' is unknown so the code cannot be validated");
- return true;
- } catch (Exception e) {
- return true;
- }
- }
- }
-
- private boolean startsWithButIsNot(String system, String... uri) {
- for (String s : uri)
- if (!system.equals(s) && system.startsWith(s))
- return true;
- return false;
- }
-
-
- private boolean hasErrors(List errors) {
- if (errors != null) {
- for (ValidationMessage vm : errors) {
- if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) {
- return true;
- }
- }
- }
- return false;
- }
-
- private void checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
- List codings = new ArrayList();
- focus.getNamedChildren("coding", codings);
- if (pattern) {
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() >= fixed.getCoding().size(),
- "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size())
- + " coding elements")) {
- for (int i = 0; i < fixed.getCoding().size(); i++) {
- Coding fixedCoding = fixed.getCoding().get(i);
- boolean found = false;
- List allErrorsFixed = new ArrayList<>();
- List errorsFixed;
- for (int j = 0; j < codings.size() && !found; ++j) {
- errorsFixed = new ArrayList<>();
- checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern);
- if (!hasErrors(errorsFixed)) {
- found = true;
- } else {
- errorsFixed
- .stream()
- .filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal())
- .forEach(t -> allErrorsFixed.add(t));
- }
- }
- if (!found) {
- // The argonaut DSTU2 labs profile requires userSelected=false on the category.coding and this
- // needs to produce an understandable error message
- String message = "Expected CodeableConcept " + (pattern ? "pattern" : "fixed value") + " not found for" +
- " system: " + fixedCoding.getSystemElement().asStringValue() +
- " code: " + fixedCoding.getCodeElement().asStringValue() +
- " display: " + fixedCoding.getDisplayElement().asStringValue();
- if (fixedCoding.hasUserSelected()) {
- message += " userSelected: " + fixedCoding.getUserSelected();
- }
- message += " - Issues: " + allErrorsFixed;
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, message);
- }
- }
- }
- } else {
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(),
- "Expected " + Integer.toString(fixed.getCoding().size()) + " but found " + Integer.toString(codings.size())
- + " coding elements")) {
- for (int i = 0; i < codings.size(); i++)
- checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus);
- }
- }
- }
-
- private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack) {
- boolean res = true;
- if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
- ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
- if (binding.hasValueSet()) {
- ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found by validator")) {
- try {
- CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
- if (!cc.hasCoding()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl());
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code must be provided from the value set " + describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) + " (max value set " + valueset.getUrl() + ")");
- else
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ")");
- }
- } else {
- long t = System.nanoTime();
-
- // Check whether the codes are appropriate for the type of binding we have
- boolean bindingsOk = true;
- if (binding.getStrength() != BindingStrength.EXAMPLE) {
- boolean atLeastOneSystemIsSupported = false;
- for (Coding nextCoding : cc.getCoding()) {
- String nextSystem = nextCoding.getSystem();
- if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
- atLeastOneSystemIsSupported = true;
- break;
- }
- }
-
- if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
- // ignore this since we can't validate but it doesn't matter..
- } else {
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang).checkValueSetOnly(), cc, valueset); // we're going to validate the codings directly, so only check the valueset
- if (!vr.isOk()) {
- bindingsOk = false;
- if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code from this value set is required (class = " + vr.getErrorClass().toString() + ")");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
- else if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code should come from this value set unless it has no suitable code (class = " + vr.getErrorClass().toString() + ")");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code is recommended to come from this value set (class = " + vr.getErrorClass().toString() + ")");
- }
- }
- } else {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
- if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code) (codes = " + ccSummary(cc) + ")");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set) (codes = " + ccSummary(cc) + ")");
- }
- }
- }
- } else if (vr.getMessage() != null) {
- res = false;
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
- } else {
- res = false;
- }
- }
- // Then, for any codes that are in code systems we are able
- // to validate, we'll validate that the codes actually exist
- if (bindingsOk) {
- for (Coding nextCoding : cc.getCoding()) {
- if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem())) {
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang).noCheckValueSetMembership(), nextCoding, valueset);
- if (vr.getSeverity() != null) {
- if (vr.getSeverity() == IssueSeverity.INFORMATION) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
- } else if (vr.getSeverity() == IssueSeverity.WARNING) {
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
- } else {
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
- }
- }
- }
- }
- }
- txTime = txTime + (System.nanoTime() - t);
- }
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating CodeableConcept");
- }
- }
- } else if (binding.hasValueSet()) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
- } else if (!noBindingMsgSuppressed) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
- }
- }
- }
- return res;
- }
-
- private boolean checkTerminologyCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) {
- boolean res = true;
- if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
- ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing (cc)")) {
- if (binding.hasValueSet()) {
- ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found by validator")) {
- try {
- CodeableConcept cc = convertToCodeableConcept(element, logical);
- if (!cc.hasCoding()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ")");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code must be provided from the value set " + describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")) + " (max value set " + valueset.getUrl() + ")");
- else
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code should be provided from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ")");
- }
- } else {
- long t = System.nanoTime();
-
- // Check whether the codes are appropriate for the type of binding we have
- boolean bindingsOk = true;
- if (binding.getStrength() != BindingStrength.EXAMPLE) {
- boolean atLeastOneSystemIsSupported = false;
- for (Coding nextCoding : cc.getCoding()) {
- String nextSystem = nextCoding.getSystem();
- if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
- atLeastOneSystemIsSupported = true;
- break;
- }
- }
-
- if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
- // ignore this since we can't validate but it doesn't matter..
- } else {
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset); // we're going to validate the codings directly
- if (!vr.isOk()) {
- bindingsOk = false;
- if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code from this value set is required (class = " + vr.getErrorClass().toString() + ")");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
- else if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code should come from this value set unless it has no suitable code (class = " + vr.getErrorClass().toString() + ")");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet()) + " and a code is recommended to come from this value set (class = " + vr.getErrorClass().toString() + ")");
- }
- }
- } else {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
- if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code should come from this value set unless it has no suitable code) (codes = " + ccSummary(cc) + ")");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code is recommended to come from this value set) (codes = " + ccSummary(cc) + ")");
- }
- }
- }
- } else if (vr.getMessage() != null) {
- res = false;
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
- } else {
- res = false;
- }
- }
- // Then, for any codes that are in code systems we are able
- // to validate, we'll validate that the codes actually exist
- if (bindingsOk) {
- for (Coding nextCoding : cc.getCoding()) {
- String nextCode = nextCoding.getCode();
- String nextSystem = nextCoding.getSystem();
- if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), nextSystem, nextCode, null);
- if (!vr.isOk()) {
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Code {0} is not a valid code in code system {1}", nextCode, nextSystem);
- }
- }
- }
- }
- txTime = txTime + (System.nanoTime() - t);
- }
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating CodeableConcept");
- }
- // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding.
- if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) {
- checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical);
- }
- }
- } else if (binding.hasValueSet()) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
- } else if (!noBindingMsgSuppressed) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
- }
- }
- }
- return res;
- }
-
- private void checkTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) {
- Coding c = convertToCoding(element, logical);
- String code = c.getCode();
- String system = c.getSystem();
- String display = c.getDisplay();
- 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, checkDisplay, stack))
- if (theElementCntext != null && theElementCntext.hasBinding()) {
- ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
- if (binding.hasValueSet()) {
- ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found by validator")) {
- try {
- long t = System.nanoTime();
- ValidationResult vr = null;
- if (binding.getStrength() != BindingStrength.EXAMPLE) {
- vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
- }
- txTime = txTime + (System.nanoTime() - t);
- if (vr != null && !vr.isOk()) {
- if (vr.IsNoService())
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided could not be validated in the absence of a terminology server");
- else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code from this value set is required");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
- else if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code should come from this value set unless it has no suitable code");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is recommended to come from this value set");
- }
- }
- } else if (binding.getStrength() == BindingStrength.REQUIRED)
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is required from this value set" + (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
- else
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code should come from this value set unless it has no suitable code" + (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is recommended to come from this value set" + (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
- }
- }
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating Coding");
- }
- }
- } else if (binding.hasValueSet()) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
- } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
- }
- }
- }
- } catch (Exception e) {
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating Coding: " + e.toString());
- }
- }
- }
-
- private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) {
- CodeableConcept res = new CodeableConcept();
- for (ElementDefinition ed : logical.getSnapshot().getElement()) {
- if (Utilities.charCount(ed.getPath(), '.') == 1) {
- List maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
- for (String m : maps) {
- String name = tail(ed.getPath());
- List list = new ArrayList<>();
- element.getNamedChildren(name, list);
- if (!list.isEmpty()) {
- if ("Coding.code".equals(m)) {
- res.getCodingFirstRep().setCode(list.get(0).primitiveValue());
- } else if ("Coding.system[fmt:OID]".equals(m)) {
- String oid = list.get(0).primitiveValue();
- String url = context.oid2Uri(oid);
- if (url != null) {
- res.getCodingFirstRep().setSystem(url);
- } else {
- res.getCodingFirstRep().setSystem("urn:oid:" + oid);
- }
- } else if ("Coding.version".equals(m)) {
- res.getCodingFirstRep().setVersion(list.get(0).primitiveValue());
- } else if ("Coding.display".equals(m)) {
- res.getCodingFirstRep().setDisplay(list.get(0).primitiveValue());
- } else if ("CodeableConcept.text".equals(m)) {
- res.setText(list.get(0).primitiveValue());
- } else if ("CodeableConcept.coding".equals(m)) {
- StructureDefinition c = context.fetchTypeDefinition(ed.getTypeFirstRep().getCode());
- for (Element e : list) {
- res.addCoding(convertToCoding(e, c));
- }
- }
- }
- }
- }
- }
- return res;
- }
-
- private Coding convertToCoding(Element element, StructureDefinition logical) {
- Coding res = new Coding();
- for (ElementDefinition ed : logical.getSnapshot().getElement()) {
- if (Utilities.charCount(ed.getPath(), '.') == 1) {
- List maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
- for (String m : maps) {
- String name = tail(ed.getPath());
- List list = new ArrayList<>();
- element.getNamedChildren(name, list);
- if (!list.isEmpty()) {
- if ("Coding.code".equals(m)) {
- res.setCode(list.get(0).primitiveValue());
- } else if ("Coding.system[fmt:OID]".equals(m)) {
- String oid = list.get(0).primitiveValue();
- String url = context.oid2Uri(oid);
- if (url != null) {
- res.setSystem(url);
- } else {
- res.setSystem("urn:oid:" + oid);
- }
- } else if ("Coding.version".equals(m)) {
- res.setVersion(list.get(0).primitiveValue());
- } else if ("Coding.display".equals(m)) {
- res.setDisplay(list.get(0).primitiveValue());
- }
- }
- }
- }
- }
- return res;
- }
-
- private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) {
- // TODO Auto-generated method stub
- ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
- try {
- long t = System.nanoTime();
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset);
- txTime = txTime + (System.nanoTime() - t);
- if (!vr.isOk()) {
- if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided could be validated against the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + "), (error = " + vr.getMessage() + ")");
- else
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating CodeableConcept using maxValueSet");
- }
- }
- }
-
- private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) {
- // TODO Auto-generated method stub
- ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
- try {
- long t = System.nanoTime();
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
- txTime = txTime + (System.nanoTime() - t);
- if (!vr.isOk()) {
- if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided could not be validated against the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + "), (error = " + vr.getMessage() + ")");
- else
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + ", and a code from this value set is required) (code = " + c.getSystem() + "#" + c.getCode() + ")");
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating CodeableConcept using maxValueSet");
- }
- }
- }
-
- private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) {
- // TODO Auto-generated method stub
- ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(maxVSUrl) + " not found by validator")) {
- try {
- long t = System.nanoTime();
- ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), value, valueset);
- txTime = txTime + (System.nanoTime() - t);
- if (!vr.isOk()) {
- if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided could not be validated against the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + "), (error = " + vr.getMessage() + ")");
- else
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + "), and a code from this value set is required) (code = " + value + "), (error = " + vr.getMessage() + ")");
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating CodeableConcept using maxValueSet");
- }
- }
- }
-
- private String ccSummary(CodeableConcept cc) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (Coding c : cc.getCoding())
- b.append(c.getSystem() + "#" + c.getCode());
- return b.toString();
- }
-
- private void checkCoding(List errors, String path, Element focus, Coding fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
- checkFixedValue(errors, path + ".version", focus.getNamedChild("version"), fixed.getVersionElement(), fixedSource, "version", focus, pattern);
- checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
- checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern);
- checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern);
- }
-
- private void checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) {
- String code = element.getNamedChildValue("code");
- String system = element.getNamedChildValue("system");
- String display = element.getNamedChildValue("display");
- 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, checkDisplay, stack))
- if (theElementCntext != null && theElementCntext.hasBinding()) {
- ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null, "Binding for " + path + " missing")) {
- if (binding.hasValueSet()) {
- ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null, "ValueSet " + describeReference(binding.getValueSet()) + " not found by validator")) {
- try {
- Coding c = ObjectConverter.readAsCoding(element);
- long t = System.nanoTime();
- ValidationResult vr = null;
- if (binding.getStrength() != BindingStrength.EXAMPLE) {
- vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
- }
- txTime = txTime + (System.nanoTime() - t);
- if (vr != null && !vr.isOk()) {
- if (vr.IsNoService())
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided could not be validated in the absence of a terminology server");
- else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
- if (binding.getStrength() == BindingStrength.REQUIRED)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code from this value set is required");
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
- else if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code should come from this value set unless it has no suitable code");
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "Could not confirm that the codes provided are in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is recommended to come from this value set");
- }
- }
- } else if (binding.getStrength() == BindingStrength.REQUIRED)
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is required from this value set. " + getErrorMessage(vr.getMessage()));
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
- else
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code should come from this value set unless it has no suitable code. " + getErrorMessage(vr.getMessage()));
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is recommended to come from this value set. " + getErrorMessage(vr.getMessage()));
- }
- }
- }
- } catch (Exception e) {
- warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating Coding");
- }
- }
- } else if (binding.hasValueSet()) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding by URI reference cannot be checked");
- } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Binding for path " + path + " has no source, so can't be checked");
- }
- }
- }
- } catch (Exception e) {
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "Error " + e.getMessage() + " validating Coding: " + e.toString());
- }
- }
- }
-
- 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, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
- checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
- checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
- checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
-
- }
-
- private StructureDefinition checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException {
- String url = element.getNamedChildValue("url");
- boolean isModifier = element.getName().equals("modifierExtension");
-
- long t = System.nanoTime();
- StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
- sdTime = sdTime + (System.nanoTime() - t);
- if (ex == null) {
- if (xverManager == null) {
- xverManager = new XVerExtensionManager(context);
- }
- if (xverManager.matchingUrl(url)) {
- switch (xverManager.status(url)) {
- case BadVersion:
- rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' is not valid (invalidVersion '" + xverManager.getVersion(url) + "')");
- break;
- case Unknown:
- rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' is not valid (unknown Element id '" + xverManager.getElementId(url) + "')");
- break;
- case Invalid:
- rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' is not valid (Element id '" + xverManager.getElementId(url) + "' is valid, but cannot be used in a cross-version paradigm because there has been no changes across the relevant versions)");
- break;
- case Valid:
- ex = xverManager.makeDefinition(url);
- context.generateSnapshot(ex);
- context.cacheResource(ex);
- break;
- default:
- rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false, "Extension url '" + url + "' evaluation state illegal");
- break;
- }
- } else if (extensionUrl != null && !isAbsolute(url)) {
- if (extensionUrl.equals(profile.getUrl())) {
- rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url), "Sub-extension url '" + url + "' is not defined by the Extension " + profile.getUrl());
- }
- } else if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url), "The extension " + url + " is unknown, and not allowed here")) {
- hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url), "Unknown extension " + url);
- }
- }
- if (ex != null) {
- trackUsage(ex, hostContext, element);
- if (def.getIsModifier()) {
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
- "Extension modifier mismatch: the extension element is labelled as a modifier, but the underlying extension is not");
- } else {
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
- "Extension modifier mismatch: the extension element is not labelled as a modifier, but the underlying extension is");
- }
- // two questions
- // 1. can this extension be used here?
- checkExtensionContext(errors, resource, container, ex, containerStack, hostContext);
-
- if (isModifier)
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),
- "The Extension '" + url + "' must be used as a modifierExtension");
- else
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),
- "The Extension '" + url + "' must not be used as an extension (it's a modifierExtension)");
-
- // check the type of the extension:
- Set allowedTypes = listExtensionTypes(ex);
- String actualType = getExtensionType(element);
- if (actualType == null)
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(), "The Extension '" + url + "' definition is for a simple extension, so it must contain a value, not extensions");
- else
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType), "The Extension '" + url + "' definition allows for the types " + allowedTypes.toString() + " but found type " + actualType);
-
- // 3. is the content of the extension valid?
- validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url);
-
- }
- return ex;
- }
-
- private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
- for (ElementDefinition ed : profile.getSnapshot().getElement()) {
- if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) {
- return true;
- }
- }
- return false;
- }
-
- private String getExtensionType(Element element) {
- for (Element e : element.getChildren()) {
- if (e.getName().startsWith("value")) {
- String tn = e.getName().substring(5);
- String ltn = Utilities.uncapitalize(tn);
- if (isPrimitiveType(ltn))
- return ltn;
- else
- return tn;
- }
- }
+ public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
+ ValidatorHostContext c = (ValidatorHostContext) appContext;
+ if (externalHostServices != null)
+ return externalHostServices.resolveConstant(c.getAppContext(), name, beforeContext);
+ else
return null;
}
- private Set listExtensionTypes(StructureDefinition ex) {
- ElementDefinition vd = null;
- for (ElementDefinition ed : ex.getSnapshot().getElement()) {
- if (ed.getPath().startsWith("Extension.value")) {
- vd = ed;
- break;
- }
- }
- Set res = new HashSet();
- if (vd != null && !"0".equals(vd.getMax())) {
- for (TypeRefComponent tr : vd.getType()) {
- res.add(tr.getWorkingCode());
- }
- }
+ @Override
+ public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
+ ValidatorHostContext c = (ValidatorHostContext) appContext;
+ if (externalHostServices != null)
+ return externalHostServices.resolveConstantType(c.getAppContext(), name);
+ else
+ return null;
+ }
+
+ @Override
+ public boolean log(String argument, List focus) {
+ if (externalHostServices != null)
+ return externalHostServices.log(argument, focus);
+ else
+ return false;
+ }
+
+ @Override
+ public FunctionDetails resolveFunction(String functionName) {
+ throw new Error("Not done yet (ValidatorHostServices.resolveFunction): " + functionName);
+ }
+
+ @Override
+ public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException {
+ throw new Error("Not done yet (ValidatorHostServices.checkFunction)");
+ }
+
+ @Override
+ public List executeFunction(Object appContext, String functionName, List> parameters) {
+ throw new Error("Not done yet (ValidatorHostServices.executeFunction)");
+ }
+
+ @Override
+ public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
+ ValidatorHostContext c = (ValidatorHostContext) appContext;
+
+ if (refContext != null && refContext.hasUserData("validator.bundle.resolution")) {
+ return (Base) refContext.getUserData("validator.bundle.resolution");
+ }
+
+ if (c.getAppContext() instanceof Element) {
+ Element bnd = (Element) c.getAppContext();
+ Base res = resolveInBundle(url, bnd);
+ if (res != null)
+ return res;
+ }
+ Base res = resolveInBundle(url, c.getResource());
+ if (res != null)
return res;
+ res = resolveInBundle(url, c.getContainer());
+ if (res != null)
+ return res;
+
+ if (externalHostServices != null)
+ return externalHostServices.resolveReference(c.getAppContext(), url, refContext);
+ else if (fetcher != null)
+ try {
+ return fetcher.fetch(c.getAppContext(), url);
+ } catch (IOException e) {
+ throw new FHIRException(e);
+ }
+ else
+ throw new Error("Not done yet - resolve " + url + " locally (2)");
+
}
- private boolean checkExtensionContext(List errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext) {
- String extUrl = definition.getUrl();
- boolean ok = false;
- CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
- List plist = new ArrayList<>();
- plist.add(stripIndexes(stack.getLiteralPath()));
- for (String s : stack.getLogicalPaths()) {
- String p = stripIndexes(s);
- // all extensions are always allowed in ElementDefinition.example.value, and in fixed and pattern values. TODO: determine the logical paths from the path stated in the element definition....
- if (Utilities.existsInList(p, "ElementDefinition.example.value", "ElementDefinition.pattern", "ElementDefinition.fixed")) {
- return true;
- }
- plist.add(p);
-
+ public Base resolveInBundle(String url, Element bnd) {
+ if (bnd == null)
+ return null;
+ if (bnd.fhirType().equals("Bundle")) {
+ for (Element be : bnd.getChildrenByName("entry")) {
+ Element res = be.getNamedChild("resource");
+ if (res != null) {
+ String fullUrl = be.getChildValue("fullUrl");
+ String rt = res.fhirType();
+ String id = res.getChildValue("id");
+ if (url.equals(fullUrl))
+ return res;
+ if (url.equals(rt + "/" + id))
+ return res;
+ }
}
+ }
+ return null;
+ }
- for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) {
- if (ok) {
- break;
- }
- if (ctxt.getType() == ExtensionContextType.ELEMENT) {
- String en = ctxt.getExpression();
- contexts.append("e:" + en);
- if ("Element".equals(en)) {
- ok = true;
- } else if (en.equals("Resource") && container.isResource()) {
- ok = true;
- }
- for (String p : plist) {
- if (ok) {
- break;
- }
- if (p.equals(en)) {
- ok = true;
- } else {
- String pn = p;
- String pt = "";
- if (p.contains(".")) {
- pn = p.substring(0, p.indexOf("."));
- pt = p.substring(p.indexOf("."));
- }
- StructureDefinition sd = context.fetchTypeDefinition(pn);
- while (sd != null) {
- if ((sd.getType() + pt).equals(en)) {
- ok = true;
- break;
- }
- if (sd.getBaseDefinition() != null) {
- sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
- } else {
- sd = null;
- }
- }
- }
- }
- } else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
- contexts.append("x:" + ctxt.getExpression());
- NodeStack estack = stack.parent;
- if (estack != null && estack.getElement().fhirType().equals("Extension")) {
- String ext = estack.element.getNamedChildValue("url");
- if (ctxt.getExpression().equals(ext)) {
- ok = true;
- }
- }
- } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
- contexts.append("p:" + ctxt.getExpression());
- // The context is all elements that match the FHIRPath query found in the expression.
- List res = fpe.evaluate(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(ctxt.getExpression()));
- if (res.contains(container)) {
- ok = true;
- }
- } else {
- throw new Error("Unrecognised extension context " + ctxt.getTypeElement().asStringValue());
- }
+ @Override
+ public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
+ ValidatorHostContext ctxt = (ValidatorHostContext) appContext;
+ StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
+ if (sd == null) {
+ throw new FHIRException("Unable to resolve " + url);
+ }
+ InstanceValidator self = InstanceValidator.this;
+ List valerrors = new ArrayList();
+ if (item instanceof Resource) {
+ try {
+ Element e = new ObjectConverter(context).convert((Resource) item);
+ self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(e));
+ } catch (IOException e1) {
+ throw new FHIRException(e1);
}
- if (!ok) {
- rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, false, "The extension " + extUrl + " is not allowed to be used at this point (allowed = " + contexts.toString() + "; this element is [" + plist.toString() + ")");
- return false;
+ } else if (item instanceof Element) {
+ Element e = (Element) item;
+ if (e.isResource()) {
+ self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(e));
} else {
- if (definition.hasContextInvariant()) {
- for (StringType s : definition.getContextInvariant()) {
- if (!fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(s.getValue()))) {
- rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, false,
- "The extension " + extUrl + " is not allowed to be used at this point (based on context invariant '" + s.getValue() + "')");
- return false;
- }
- }
- }
- return true;
+ throw new FHIRException("Not supported yet");
}
+ } else
+ throw new NotImplementedException("Not done yet (ValidatorHostServices.conformsToProfile), when item is not an element");
+ boolean ok = true;
+ List record = new ArrayList<>();
+ for (ValidationMessage v : valerrors) {
+ ok = ok && !v.getLevel().isError();
+ if (v.getLevel().isError() || v.isSlicingHint()) {
+ record.add(v);
+ }
+ }
+ if (!ok && !record.isEmpty()) {
+ ctxt.sliceNotes(url, record);
+ }
+ return ok;
}
- private List fixContexts(String extUrl, List list) {
- List res = new ArrayList<>();
- for (StructureDefinitionContextComponent ctxt : list) {
- res.add(ctxt.copy());
- }
- if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type".equals(extUrl)) {
- list.get(0).setExpression("ElementDefinition.type");
- }
- if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
- list.get(1).setExpression("ElementDefinition.type");
- }
- return list;
- }
-
- private String stripIndexes(String path) {
- boolean skip = false;
- StringBuilder b = new StringBuilder();
- for (char c : path.toCharArray()) {
- if (skip) {
- if (c == ']') {
- skip = false;
- }
- } else if (c == '[') {
- skip = true;
- } else {
- b.append(c);
- }
- }
- return b.toString();
- }
-
- private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent) {
- checkFixedValue(errors, path, focus, fixed, fixedSource, propName, parent, false);
- }
-
- @SuppressWarnings("rawtypes")
- private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern) {
- if ((fixed == null || fixed.isEmpty()) && focus == null) {
- ; // this is all good
- } else if ((fixed == null || fixed.isEmpty()) && focus != null) {
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern, "The element " + focus.getName() + " is present in the instance but not allowed in the applicable " + (pattern ? "pattern" : "fixed value") + " specified in profile");
- } else if (fixed != null && !fixed.isEmpty() && focus == null) {
- rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false, "Missing element '" + propName + "' - required by fixed value assigned in profile " + fixedSource);
- } else {
- String value = focus.primitiveValue();
- if (fixed instanceof org.hl7.fhir.r5.model.BooleanType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.InstantType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.InstantType) fixed).getValue().toString(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.InstantType) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.CodeType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.CodeType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.CodeType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.StringType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.StringType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.StringType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.UriType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UriType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.UriType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.DateType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateType) fixed).getValue().toString(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.DateType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue().toString(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.OidType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.OidType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.OidType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.UuidType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.UuidType) fixed).getValue() + "'");
- else if (fixed instanceof org.hl7.fhir.r5.model.IdType)
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value),
- "Value is '" + value + "' but must be '" + ((org.hl7.fhir.r5.model.IdType) fixed).getValue() + "'");
- else if (fixed instanceof Quantity)
- checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern);
- else if (fixed instanceof Address)
- checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern);
- else if (fixed instanceof ContactPoint)
- checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern);
- else if (fixed instanceof Attachment)
- checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern);
- else if (fixed instanceof Identifier)
- checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern);
- else if (fixed instanceof Coding)
- checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern);
- else if (fixed instanceof HumanName)
- checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern);
- else if (fixed instanceof CodeableConcept)
- checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern);
- else if (fixed instanceof Timing)
- checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern);
- else if (fixed instanceof Period)
- checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern);
- else if (fixed instanceof Range)
- checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern);
- else if (fixed instanceof Ratio)
- checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern);
- else if (fixed instanceof SampledData)
- checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern);
-
+ @Override
+ public ValueSet resolveValueSet(Object appContext, String url) {
+ ValidatorHostContext c = (ValidatorHostContext) appContext;
+ if (c.getProfile() != null && url.startsWith("#")) {
+ for (Resource r : c.getProfile().getContained()) {
+ if (r.getId().equals(url.substring(1))) {
+ if (r instanceof ValueSet)
+ return (ValueSet) r;
else
- rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName());
- List extensions = new ArrayList();
- focus.getNamedChildren("extension", extensions);
- if (fixed.getExtension().size() == 0) {
- rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0, "No extensions allowed, as the specified fixed value doesn't contain any extensions");
- } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(),
- "Extensions count mismatch: expected " + Integer.toString(fixed.getExtension().size()) + " but found " + Integer.toString(extensions.size()))) {
- for (Extension e : fixed.getExtension()) {
- Element ex = getExtensionByUrl(extensions, e.getUrl());
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, "Extension count mismatch: unable to find extension: " + e.getUrl())) {
- checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"));
- }
- }
- }
+ throw new FHIRException("Reference " + url + " refers to a " + r.fhirType() + " not a ValueSet");
+ }
}
+ return null;
+ }
+ return context.fetchResource(ValueSet.class, url);
}
- private void checkHumanName(List errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
- checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
- checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
+ }
- List parts = new ArrayList();
- focus.getNamedChildren("family", parts);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(),
- "Expected " + (fixed.hasFamily() ? "1" : "0") + " but found " + Integer.toString(parts.size()) + " family elements")) {
- for (int i = 0; i < parts.size(); i++)
- checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern);
- }
- focus.getNamedChildren("given", parts);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(),
- "Expected " + Integer.toString(fixed.getGiven().size()) + " but found " + Integer.toString(parts.size()) + " given elements")) {
- for (int i = 0; i < parts.size(); i++)
- checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern);
- }
- focus.getNamedChildren("prefix", parts);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(),
- "Expected " + Integer.toString(fixed.getPrefix().size()) + " but found " + Integer.toString(parts.size()) + " prefix elements")) {
- for (int i = 0; i < parts.size(); i++)
- checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern);
- }
- focus.getNamedChildren("suffix", parts);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(),
- "Expected " + Integer.toString(fixed.getSuffix().size()) + " but found " + Integer.toString(parts.size()) + " suffix elements")) {
- for (int i = 0; i < parts.size(); i++)
- checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern);
- }
+ private IWorkerContext context;
+ private FHIRPathEngine fpe;
+
+ // configuration items
+ private CheckDisplayOption checkDisplay;
+ private boolean anyExtensionsAllowed;
+ private boolean errorForUnknownProfiles;
+ private boolean noInvariantChecks;
+ private boolean noTerminologyChecks;
+ private boolean hintAboutNonMustSupport;
+ private boolean showMessagesFromReferences;
+ private BestPracticeWarningLevel bpWarnings;
+ private String validationLanguage;
+ private boolean baseOnly;
+
+ private List extensionDomains = new ArrayList();
+
+ private IdStatus resourceIdRule;
+ private boolean allowXsiLocation;
+
+ // used during the build process to keep the overall volume of messages down
+ private boolean suppressLoincSnomedMessages;
+
+ // time tracking
+ private long overall = 0;
+ private long txTime = 0;
+ private long sdTime = 0;
+ private long loadTime = 0;
+ private long fpeTime = 0;
+
+ private boolean noBindingMsgSuppressed;
+ private boolean debug;
+ private Map fetchCache = new HashMap<>();
+ private HashMap resourceTracker = new HashMap<>();
+ private IValidatorResourceFetcher fetcher;
+ long time = 0;
+ private IEvaluationContext externalHostServices;
+ private boolean noExtensibleWarnings;
+ private String serverBase;
+
+ private EnableWhenEvaluator myEnableWhenEvaluator = new EnableWhenEvaluator();
+ private String executionId;
+ private XVerExtensionManager xverManager;
+ private IValidationProfileUsageTracker tracker;
+ private ValidatorHostServices validatorServices;
+ private boolean assumeValidRestReferences;
+ private boolean allowExamples;
+ private ResourceBundle messages =
+ ResourceBundle.getBundle("Messages", Locale.US);
+
+ public InstanceValidator(IWorkerContext theContext, IEvaluationContext hostServices) {
+ super();
+ this.context = theContext;
+ this.externalHostServices = hostServices;
+ fpe = new FHIRPathEngine(context);
+ validatorServices = new ValidatorHostServices();
+ fpe.setHostServices(validatorServices);
+ if (theContext.getVersion().startsWith("3.0") || theContext.getVersion().startsWith("1.0"))
+ fpe.setLegacyMode(true);
+ source = Source.InstanceValidator;
+ }
+
+ @Override
+ public boolean isNoExtensibleWarnings() {
+ return noExtensibleWarnings;
+ }
+
+ @Override
+ public IResourceValidator setNoExtensibleWarnings(boolean noExtensibleWarnings) {
+ this.noExtensibleWarnings = noExtensibleWarnings;
+ return this;
+ }
+
+ @Override
+ public boolean isShowMessagesFromReferences() {
+ return showMessagesFromReferences;
+ }
+
+ @Override
+ public void setShowMessagesFromReferences(boolean showMessagesFromReferences) {
+ this.showMessagesFromReferences = showMessagesFromReferences;
+ }
+
+ @Override
+ public boolean isNoInvariantChecks() {
+ return noInvariantChecks;
+ }
+
+ @Override
+ public IResourceValidator setNoInvariantChecks(boolean value) {
+ this.noInvariantChecks = value;
+ return this;
+ }
+
+ public IValidatorResourceFetcher getFetcher() {
+ return this.fetcher;
+ }
+
+ public IResourceValidator setFetcher(IValidatorResourceFetcher value) {
+ this.fetcher = value;
+ return this;
+ }
+
+ public IValidationProfileUsageTracker getTracker() {
+ return this.tracker;
+ }
+
+ public IResourceValidator setTracker(IValidationProfileUsageTracker value) {
+ this.tracker = value;
+ return this;
+ }
+
+
+ public boolean isHintAboutNonMustSupport() {
+ return hintAboutNonMustSupport;
+ }
+
+ public void setHintAboutNonMustSupport(boolean hintAboutNonMustSupport) {
+ this.hintAboutNonMustSupport = hintAboutNonMustSupport;
+ }
+
+ public boolean isAssumeValidRestReferences() {
+ return this.assumeValidRestReferences;
+ }
+
+ public void setAssumeValidRestReferences(boolean value) {
+ this.assumeValidRestReferences = value;
+ }
+
+ public boolean isAllowExamples() {
+ return this.allowExamples;
+ }
+
+ public void setAllowExamples(boolean value) {
+ this.allowExamples = value;
+ }
+
+
+ private boolean allowUnknownExtension(String url) {
+ if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression"))
+ // Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
+ return true;
+ for (String s : extensionDomains)
+ if (url.startsWith(s))
+ return true;
+ return anyExtensionsAllowed;
+ }
+
+ private boolean isKnownExtension(String url) {
+ // Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with
+ if ((allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION))
+ return true;
+ for (String s : extensionDomains)
+ if (url.startsWith(s))
+ return true;
+ return false;
+ }
+
+ private void bpCheck(List errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
+ if (bpWarnings != null) {
+ switch (bpWarnings) {
+ case Error:
+ rule(errors, invalid, line, col, literalPath, test, message);
+ break;
+ case Warning:
+ warning(errors, invalid, line, col, literalPath, test, message);
+ break;
+ case Hint:
+ hint(errors, invalid, line, col, literalPath, test, message);
+ break;
+ default: // do nothing
+ break;
+ }
}
+ }
- private void checkIdentifier(List errors, String path, Element element, ElementDefinition context) {
- String system = element.getNamedChildValue("system");
- rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system), "Identifier.system must be an absolute reference, not a local reference");
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format) throws FHIRException {
+ return validate(appContext, errors, stream, format, new ArrayList<>());
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
}
+ return validate(appContext, errors, stream, format, profiles);
+ }
- private void checkIdentifier(List errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
- checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), fixedSource, "type", focus, pattern);
- checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
- checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
- checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
- checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern);
+ private StructureDefinition getSpecifiedProfile(String profile) {
+ StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile);
+ if (sd == null) {
+ throw new FHIRException("Unable to locate the profile '" + profile + "' in order to validate against it");
}
+ return sd;
+ }
- private void checkPeriod(List errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern);
- checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern);
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, InputStream stream, FhirFormat format, List profiles) throws FHIRException {
+ ParserBase parser = Manager.makeParser(context, format);
+ if (parser instanceof XmlParser)
+ ((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
+ parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
+ long t = System.nanoTime();
+ Element e;
+ try {
+ e = parser.parse(stream);
+ } catch (IOException e1) {
+ throw new FHIRException(e1);
}
+ loadTime = System.nanoTime() - t;
+ if (e != null)
+ validate(appContext, errors, e, profiles);
+ return e;
+ }
- private void checkPrimitive(Object appContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException {
- if (isBlank(e.primitiveValue())) {
- if (e.primitiveValue() == null)
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "Primitive types must have a value or must have child extensions");
- else if (e.primitiveValue().length() == 0)
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "Primitive types must have a value that is not empty");
- else if (StringUtils.isWhitespace(e.primitiveValue()))
- warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(), "Primitive types should not only be whitespace");
- 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 + "'");
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource) throws FHIRException {
+ return validate(appContext, errors, resource, new ArrayList<>());
+ }
- 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") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
- String url = e.primitiveValue();
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), "URI values cannot start with oid:");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), "URI values cannot start with uuid:");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", ""))
- // work around an old invalid example in a core package
- || "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), "URI values cannot have whitespace('" + url + "')");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), "value is longer than permitted maximum length of " + context.getMaxLength());
-
- if (type.equals("oid")) {
- if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"), "OIDs must start with urn:oid:"))
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)), "OIDs must be valid");
- }
- if (type.equals("uuid")) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"), "UUIDs must start with urn:uuid:");
- try {
- UUID.fromString(url.substring(8));
- } catch (Exception ex) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "UUIDs must be valid (" + ex.getMessage() + ")");
- }
- }
-
- // now, do we check the URI target?
- if (fetcher != null) {
- boolean found;
- try {
- found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url);
- } catch (IOException e1) {
- found = false;
- }
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, "URL value '" + url + "' does not resolve");
- }
- }
- if (type.equals("id")) {
- // work around an old issue with ElementDefinition.id
- if (!context.getPath().equals("ElementDefinition.id") && !VersionUtilities.versionsCompatible("1.4", this.context.getVersion())) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()), "id value '" + e.primitiveValue() + "' is not valid");
- }
- }
- 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")) {
- warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()), "value should not start or finish with whitespace");
- if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576, "value is longer than permitted maximum length of 1 MB (1048576 bytes)")) {
- 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());
- }
- }
- }
- if (type.equals("dateTime")) {
- warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' is outside the range of reasonable years - check for data entry error");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path,
- e.primitiveValue()
- .matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),
- "Not a valid date time");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), "if a date has a time, it must have a timezone");
- 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());
- try {
- DateTimeType dt = new DateTimeType(e.primitiveValue());
- } catch (Exception ex) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Not a valid date/time (" + ex.getMessage() + ")");
- }
- }
- if (type.equals("time")) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path,
- e.primitiveValue()
- .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"),
- "Not a valid time");
- try {
- TimeType dt = new TimeType(e.primitiveValue());
- } catch (Exception ex) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Not a valid time (" + ex.getMessage() + ")");
- }
- }
- if (type.equals("date")) {
- warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' is outside the range of reasonable years - check for data entry error");
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"),
- "Not a valid date");
- 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 value of " + context.getMaxLength());
- try {
- DateType dt = new DateType(e.primitiveValue());
- } catch (Exception ex) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Not a valid date (" + ex.getMessage() + ")");
- }
- }
- if (type.equals("base64Binary")) {
- String encoded = e.primitiveValue();
- if (isNotBlank(encoded)) {
- /*
- * Technically this is not bulletproof as some invalid base64 won't be caught,
- * but I think it's good enough. The original code used Java8 Base64 decoder
- * but I've replaced it with a regex for 2 reasons:
- * 1. This code will run on any version of Java
- * 2. This code doesn't actually decode, which is much easier on memory use for big payloads
- */
- int charCount = 0;
- for (int i = 0; i < encoded.length(); i++) {
- char nextChar = encoded.charAt(i);
- if (Character.isWhitespace(nextChar)) {
- continue;
- }
- if (Character.isLetterOrDigit(nextChar)) {
- charCount++;
- }
- if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
- charCount++;
- }
- }
-
- if (charCount > 0 && charCount % 4 != 0) {
- String value = encoded.length() < 100 ? encoded : "(snip)";
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "The value '{0}' is not a valid Base64 value", value);
- }
- }
- }
- if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
- if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()), "The value '" + e.primitiveValue() + "' is not a valid integer")) {
- Integer v = new Integer(e.getValue()).intValue();
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v), "value is greater than permitted maximum value of " + (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : ""));
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v), "value is less than permitted minimum value of " + (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : ""));
- if (type.equals("unsignedInt"))
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, "value is less than permitted minimum value of 0");
- if (type.equals("positiveInt"))
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, "value is less than permitted minimum value of 1");
- }
- }
- if (type.equals("integer64")) {
- if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()), "The value '" + e.primitiveValue() + "' is not a valid integer64")) {
- Long v = new Long(e.getValue()).longValue();
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v), "value is greater than permitted maximum value of " + (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : ""));
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v), "value is less than permitted minimum value of " + (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : ""));
- if (type.equals("unsignedInt"))
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0, "value is less than permitted minimum value of 0");
- if (type.equals("positiveInt"))
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0, "value is less than permitted minimum value of 1");
- }
- }
- if (type.equals("decimal")) {
- if (e.primitiveValue() != null) {
- DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
- if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, "The value '" + e.primitiveValue() + "' is not a valid decimal"))
- warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, "The value '" + e.primitiveValue() + "' is outside the range of commonly/reasonably supported decimals");
- }
- }
- if (type.equals("instant")) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path,
- e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"),
- "The instant '" + e.primitiveValue() + "' is not valid (by regex)");
- warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), "The value '" + e.primitiveValue() + "' is outside the range of reasonable years - check for data entry error");
- try {
- InstantType dt = new InstantType(e.primitiveValue());
- } catch (Exception ex) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Not a valid instant (" + ex.getMessage() + ")");
- }
- }
-
- if (type.equals("code") && e.primitiveValue() != null) {
- // Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
- // other than single spaces in the contents
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()), "The code '" + e.primitiveValue() + "' is not valid (whitespace rules)");
- 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());
- }
-
- if (context.hasBinding() && e.primitiveValue() != null) {
- checkPrimitiveBinding(errors, path, type, context, e, profile, node);
- }
-
- if (type.equals("xhtml")) {
- XhtmlNode xhtml = e.getXhtml();
- if (xhtml != null) { // if it is null, this is an error already noted in the parsers
- // check that the namespace is there and correct.
- String ns = xhtml.getNsDecl();
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('" + ns + "', should be '" + FormatUtilities.XHTML_NS + "')");
- // check that inner namespaces are all correct
- checkInnerNS(errors, e, path, xhtml.getChildNodes());
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), "Wrong name on the XHTML ('" + ns + "') - must start with div");
- // check that no illegal elements and attributes have been used
- checkInnerNames(errors, e, path, xhtml.getChildNodes());
- }
- }
-
- if (context.hasFixed()) {
- checkFixedValue(errors, path, e, context.getFixed(), profile.getUrl(), context.getSliceName(), null, false);
- }
- if (context.hasPattern()) {
- checkFixedValue(errors, path, e, context.getPattern(), profile.getUrl(), context.getSliceName(), null, true);
- }
-
- // for nothing to check
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
}
+ return validate(appContext, errors, resource, profiles);
+ }
- private boolean isDefinitionURL(String url) {
- return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer",
- "http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity");
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Resource resource, List profiles) throws FHIRException {
+ long t = System.nanoTime();
+ Element e;
+ try {
+ e = new ObjectConverter(context).convert(resource);
+ } catch (IOException e1) {
+ throw new FHIRException(e1);
}
+ loadTime = System.nanoTime() - t;
+ validate(appContext, errors, e, profiles);
+ return e;
+ }
- private void checkInnerNames(List errors, Element e, String path, List list) {
- for (XhtmlNode node : list) {
- if (node.getNodeType() == NodeType.Element) {
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(),
- "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
- "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
- "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
- "code", "samp", "img", "map", "area"
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element) throws FHIRException {
+ return validate(appContext, errors, element, new ArrayList<>());
+ }
- ), "Illegal element name in the XHTML ('" + node.getName() + "')");
- for (String an : node.getAttributes().keySet()) {
- boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
- "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
- // tables
- "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
+ }
+ return validate(appContext, errors, element, profiles);
+ }
- Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
- "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
- "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
- "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
- "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
- );
- if (!ok)
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, "Illegal attribute name in the XHTML ('" + an + "' on '" + node.getName() + "')");
- }
- checkInnerNames(errors, e, path, node.getChildNodes());
- }
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, org.w3c.dom.Element element, List profiles) throws FHIRException {
+ XmlParser parser = new XmlParser(context);
+ parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
+ long t = System.nanoTime();
+ Element e;
+ try {
+ e = parser.parse(element);
+ } catch (IOException e1) {
+ throw new FHIRException(e1);
+ }
+ loadTime = System.nanoTime() - t;
+ if (e != null)
+ validate(appContext, errors, e, profiles);
+ return e;
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document) throws FHIRException {
+ return validate(appContext, errors, document, new ArrayList<>());
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
+ }
+ return validate(appContext, errors, document, profiles);
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, Document document, List profiles) throws FHIRException {
+ XmlParser parser = new XmlParser(context);
+ parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
+ long t = System.nanoTime();
+ Element e;
+ try {
+ e = parser.parse(document);
+ } catch (IOException e1) {
+ throw new FHIRException(e1);
+ }
+ loadTime = System.nanoTime() - t;
+ if (e != null)
+ validate(appContext, errors, e, profiles);
+ return e;
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object) throws FHIRException {
+ return validate(appContext, errors, object, new ArrayList<>());
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
+ }
+ return validate(appContext, errors, object, profiles);
+ }
+
+ @Override
+ public org.hl7.fhir.r5.elementmodel.Element validate(Object appContext, List errors, JsonObject object, List profiles) throws FHIRException {
+ JsonParser parser = new JsonParser(context);
+ parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
+ long t = System.nanoTime();
+ Element e = parser.parse(object);
+ loadTime = System.nanoTime() - t;
+ if (e != null)
+ validate(appContext, errors, e, profiles);
+ return e;
+ }
+
+ @Override
+ public void validate(Object appContext, List errors, Element element) throws FHIRException {
+ validate(appContext, errors, element, new ArrayList<>());
+ }
+
+ @Override
+ public void validate(Object appContext, List errors, Element element, String profile) throws FHIRException {
+ ArrayList profiles = new ArrayList<>();
+ if (profile != null) {
+ profiles.add(getSpecifiedProfile(profile));
+ }
+ validate(appContext, errors, element, profiles);
+ }
+
+ @Override
+ public void validate(Object appContext, List errors, Element element, List profiles) throws FHIRException {
+ // this is the main entry point; all the other public entry points end up here coming here...
+ // so the first thing to do is to clear the internal state
+ fetchCache.clear();
+ fetchCache.put(element.fhirType() + "/" + element.getIdBase(), element);
+ resourceTracker.clear();
+ executionId = UUID.randomUUID().toString();
+ baseOnly = profiles.isEmpty();
+
+ long t = System.nanoTime();
+ if (profiles == null || profiles.isEmpty()) {
+ validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(element));
+ } else {
+ for (StructureDefinition defn : profiles) {
+ validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(element));
+ }
+ }
+ if (hintAboutNonMustSupport) {
+ checkElementUsage(errors, element, new NodeStack(element));
+ }
+ overall = System.nanoTime() - t;
+ }
+
+ private void checkElementUsage(List errors, Element element, NodeStack stack) {
+ String elementUsage = element.getUserString("elementSupported");
+ hint(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), elementUsage == null || elementUsage.equals("Y"),messages.getString("The_element__is_not_marked_as_mustSupport_in_the_profile__Consider_not_using_the_element_or_marking_the_element_as_mustSupport_in_the_profile"), element.getName(), element.getProperty().getStructure().getUrl());
+
+ if (element.hasChildren()) {
+ String prevName = "";
+ int elementCount = 0;
+ for (Element ce : element.getChildren()) {
+ if (ce.getName().equals(prevName))
+ elementCount++;
+ else {
+ elementCount = 1;
+ prevName = ce.getName();
}
+ checkElementUsage(errors, ce, stack.push(ce, elementCount, null, null));
+ }
}
+ }
- private void checkInnerNS(List errors, Element e, String path, List list) {
- for (XhtmlNode node : list) {
- if (node.getNodeType() == NodeType.Element) {
- String ns = node.getNsDecl();
- rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns), "Wrong namespace on the XHTML ('" + ns + "', should be '" + FormatUtilities.XHTML_NS + "')");
- checkInnerNS(errors, e, path, node.getChildNodes());
- }
+ private boolean check(String v1, String v2) {
+ return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
+ }
+
+ private void checkAddress(List errors, String path, Element focus, Address fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
+ checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
+ checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), fixedSource, "city", focus, pattern);
+ checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), fixedSource, "state", focus, pattern);
+ checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), fixedSource, "country", focus, pattern);
+ checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), fixedSource, "postalCode", focus, pattern);
+
+ List lines = new ArrayList();
+ focus.getNamedChildren("line", lines);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, lines.size() == fixed.getLine().size(),messages.getString("Expected__but_found__line_elements"), Integer.toString(fixed.getLine().size()), Integer.toString(lines.size()))) {
+ for (int i = 0; i < lines.size(); i++)
+ checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), fixedSource, "coding", focus, pattern);
+ }
+ }
+
+ private void checkAttachment(List errors, String path, Element focus, Attachment fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), fixedSource, "contentType", focus, pattern);
+ checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), fixedSource, "language", focus, pattern);
+ checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern);
+ checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), fixedSource, "url", focus, pattern);
+ checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), fixedSource, "size", focus, pattern);
+ checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), fixedSource, "hash", focus, pattern);
+ checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), fixedSource, "title", focus, pattern);
+ }
+
+ // public API
+ private boolean checkCode(List errors, Element element, String path, String code, String system, String display, boolean checkDisplay, NodeStack stack) throws TerminologyServiceException {
+ long t = System.nanoTime();
+ boolean ss = context.supportsSystem(system);
+ txTime = txTime + (System.nanoTime() - t);
+ if (ss) {
+ t = System.nanoTime();
+ ValidationResult s = context.validateCode(new ValidationOptions(stack.workingLang), system, code, checkDisplay ? display : null);
+ txTime = txTime + (System.nanoTime() - t);
+ if (s == null)
+ return true;
+ if (s.isOk()) {
+ if (s.getMessage() != null)
+ txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
+ return true;
+ }
+ if (s.getErrorClass() != null && s.getErrorClass().isInfrastructure())
+ txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
+ else if (s.getSeverity() == IssueSeverity.INFORMATION)
+ txHint(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
+ else if (s.getSeverity() == IssueSeverity.WARNING)
+ txWarning(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage());
+ else
+ return txRule(errors, s.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, s == null, s.getMessage() + " for '" + system + "#" + code + "'");
+ return true;
+ } else if (system.startsWith("http://hl7.org/fhir")) {
+ if (Utilities.existsInList(system, "http://hl7.org/fhir/sid/icd-10", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/icd-10", "http://hl7.org/fhir/sid/icd-10-cm", "http://hl7.org/fhir/sid/icd-9", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/srt"))
+ return true; // else don't check these (for now)
+ else if (system.startsWith("http://hl7.org/fhir/test"))
+ return true; // we don't validate these
+ else {
+ CodeSystem cs = getCodeSystem(system);
+ if (rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, cs != null,messages.getString("Unknown_Code_System_"), system)) {
+ ConceptDefinitionComponent def = getCodeDefinition(cs, code);
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, def != null,messages.getString("Unknown_Code_"), system, code))
+ return warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, display == null || display.equals(def.getDisplay()),messages.getString("Display_should_be_"), def.getDisplay());
}
- }
-
- private void checkPrimitiveBinding(List errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
- // We ignore bindings that aren't on string, uri or code
- if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
- return;
+ return false;
+ }
+ } else if (context.isNoTerminologyServer() && Utilities.existsInList(system, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
+ return true; // no checks in this case
+ } else if (startsWithButIsNot(system, "http://snomed.info/sct", "http://loinc.org", "http://unitsofmeasure.org", "http://www.nlm.nih.gov/research/umls/rxnorm")) {
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Invalid_System_URI_"), system);
+ return false;
+ } else {
+ try {
+ if (context.fetchResourceWithException(ValueSet.class, system) != null) {
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Invalid_System_URI___cannot_use_a_value_set_URI_as_a_system"), system);
+ // Lloyd: This error used to prohibit checking for downstream issues, but there are some cases where that checking needs to occur. Please talk to me before changing the code back.
}
- if (noTerminologyChecks)
- return;
-
- String value = element.primitiveValue();
- // System.out.println("check "+value+" in "+path);
-
- // firstly, resolve the value set
- ElementDefinitionBindingComponent binding = elementContext.getBinding();
- if (binding.hasValueSet()) {
- ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
- if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found by validator", describeReference(binding.getValueSet()))) {
- long t = System.nanoTime();
- ValidationResult vr = null;
- if (binding.getStrength() != BindingStrength.EXAMPLE) {
- vr = context.validateCode(new ValidationOptions(stack.workingLang), value, vs);
- }
- txTime = txTime + (System.nanoTime() - t);
- if (vr != null && !vr.isOk()) {
- if (vr.IsNoService())
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('" + value + "') could not be validated in the absence of a terminology server");
- else if (binding.getStrength() == BindingStrength.REQUIRED)
- txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('" + value + "') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set)" + getErrorMessage(vr.getMessage()));
- else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
- if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
- checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack);
- else if (!noExtensibleWarnings)
- txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('" + value + "') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code should come from this value set unless it has no suitable code)" + getErrorMessage(vr.getMessage()));
- } else if (binding.getStrength() == BindingStrength.PREFERRED) {
- if (baseOnly) {
- txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('" + value + "') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is recommended to come from this value set)" + getErrorMessage(vr.getMessage()));
- }
- }
- }
- }
- } else if (!noBindingMsgSuppressed)
- hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"), "Binding has no source, so can't be checked");
+ hint(errors, IssueType.UNKNOWN, element.line(), element.col(), path, false,messages.getString("Code_System_URI__is_unknown_so_the_code_cannot_be_validated"), system);
+ return true;
+ } catch (Exception e) {
+ return true;
+ }
}
+ }
- private void checkQuantity(List errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
- checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern);
- checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "units", focus, pattern);
- checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
- checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
- }
+ private boolean startsWithButIsNot(String system, String... uri) {
+ for (String s : uri)
+ if (!system.equals(s) && system.startsWith(s))
+ return true;
+ return false;
+ }
- // implementation
- private void checkRange(List errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern);
- checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern);
-
- }
-
- private void checkRatio(List errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern);
- checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern);
- }
-
- private void checkReference(ValidatorHostContext hostContext, List errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException {
- Reference reference = ObjectConverter.readAsReference(element);
-
- String ref = reference.getReference();
- if (Utilities.noString(ref)) {
- if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
- warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")), "A Reference without an actual reference or identifier should have a display");
- }
- return;
- } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) {
- // special known URLs that can't be validated but are known to be valid
- return;
+ private boolean hasErrors(List errors) {
+ if (errors != null) {
+ for (ValidationMessage vm : errors) {
+ if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) {
+ return true;
}
+ }
+ }
+ return false;
+ }
- ResolvedReference we = localResolve(ref, stack, errors, path, (Element) hostContext.getAppContext(), element);
- String refType;
- if (ref.startsWith("#")) {
- refType = "contained";
- } else {
- if (we == null) {
- refType = "remote";
+ private void checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
+ List codings = new ArrayList();
+ focus.getNamedChildren("coding", codings);
+ if (pattern) {
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() >= fixed.getCoding().size(),messages.getString("Expected__but_found__coding_elements"), Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) {
+ for (int i = 0; i < fixed.getCoding().size(); i++) {
+ Coding fixedCoding = fixed.getCoding().get(i);
+ boolean found = false;
+ List allErrorsFixed = new ArrayList<>();
+ List errorsFixed;
+ for (int j = 0; j < codings.size() && !found; ++j) {
+ errorsFixed = new ArrayList<>();
+ checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, fixedSource, "coding", focus, pattern);
+ if (!hasErrors(errorsFixed)) {
+ found = true;
} else {
- refType = "bundled";
+ errorsFixed
+ .stream()
+ .filter(t -> t.getLevel().ordinal() >= IssueSeverity.ERROR.ordinal())
+ .forEach(t -> allErrorsFixed.add(t));
}
+ }
+ if (!found) {
+ // The argonaut DSTU2 labs profile requires userSelected=false on the category.coding and this
+ // needs to produce an understandable error message
+ String message = "Expected CodeableConcept " + (pattern ? "pattern" : "fixed value") + " not found for" +
+ " system: " + fixedCoding.getSystemElement().asStringValue() +
+ " code: " + fixedCoding.getCodeElement().asStringValue() +
+ " display: " + fixedCoding.getDisplayElement().asStringValue();
+ if (fixedCoding.hasUserSelected()) {
+ message += " userSelected: " + fixedCoding.getUserSelected();
+ }
+ message += " - Issues: " + allErrorsFixed;
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, message);
+ }
}
- ReferenceValidationPolicy pol = refType.equals("contained") || refType.equals("bundled") ? ReferenceValidationPolicy.CHECK_VALID : fetcher == null ? ReferenceValidationPolicy.IGNORE : fetcher.validationPolicy(hostContext.getAppContext(), path, ref);
+ }
+ } else {
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(),messages.getString("Expected__but_found__coding_elements"), Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) {
+ for (int i = 0; i < codings.size(); i++)
+ checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus);
+ }
+ }
+ }
- if (pol.checkExists()) {
- if (we == null) {
- if (fetcher == null) {
- if (!refType.equals("contained"))
- throw new FHIRException("Resource resolution services not provided");
- } else {
- Element ext = null;
- if (fetchCache.containsKey(ref)) {
- ext = fetchCache.get(ref);
- } else {
- try {
- ext = fetcher.fetch(hostContext.getAppContext(), ref);
- } catch (IOException e) {
- throw new FHIRException(e);
- }
- if (ext != null) {
- fetchCache.put(ref, ext);
- }
- }
- we = ext == null ? null : makeExternalRef(ext, path);
+ private boolean checkCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack) {
+ boolean res = true;
+ if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
+ ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null,messages.getString("Binding_for__missing_cc"), path)) {
+ if (binding.hasValueSet()) {
+ ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(binding.getValueSet()))) {
+ try {
+ CodeableConcept cc = ObjectConverter.readAsCodeableConcept(element);
+ if (!cc.hasCoding()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false, "No code provided, and a code is required from the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl());
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("No_code_provided_and_a_code_must_be_provided_from_the_value_set__max_value_set_"), describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")), valueset.getUrl());
+ else
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("No_code_provided_and_a_code_should_be_provided_from_the_value_set__"), describeReference(binding.getValueSet()), valueset.getUrl());
}
- }
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (allowExamples && (ref.contains("example.org") || ref.contains("acme.com"))) || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS), "Unable to resolve resource '" + ref + "'");
- }
+ } else {
+ long t = System.nanoTime();
- String ft;
- if (we != null)
- ft = we.getType();
+ // Check whether the codes are appropriate for the type of binding we have
+ boolean bindingsOk = true;
+ if (binding.getStrength() != BindingStrength.EXAMPLE) {
+ boolean atLeastOneSystemIsSupported = false;
+ for (Coding nextCoding : cc.getCoding()) {
+ String nextSystem = nextCoding.getSystem();
+ if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
+ atLeastOneSystemIsSupported = true;
+ break;
+ }
+ }
+
+ if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
+ // ignore this since we can't validate but it doesn't matter..
+ } else {
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang).checkValueSetOnly(), cc, valueset); // we're going to validate the codings directly, so only check the valueset
+ if (!vr.isOk()) {
+ bindingsOk = false;
+ if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_from_this_value_set_is_required_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
+ else if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ }
+ }
+ } else {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
+ if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("None_of_the_codes_provided_are_in_the_value_set___and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code_codes__"), describeReference(binding.getValueSet()), valueset.getUrl(), ccSummary(cc));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("None_of_the_codes_provided_are_in_the_value_set___and_a_code_is_recommended_to_come_from_this_value_set_codes__"), describeReference(binding.getValueSet()), valueset.getUrl(), ccSummary(cc));
+ }
+ }
+ }
+ } else if (vr.getMessage() != null) {
+ res = false;
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
+ } else {
+ res = false;
+ }
+ }
+ // Then, for any codes that are in code systems we are able
+ // to validate, we'll validate that the codes actually exist
+ if (bindingsOk) {
+ for (Coding nextCoding : cc.getCoding()) {
+ if (isNotBlank(nextCoding.getCode()) && isNotBlank(nextCoding.getSystem()) && context.supportsSystem(nextCoding.getSystem())) {
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang).noCheckValueSetMembership(), nextCoding, valueset);
+ if (vr.getSeverity() != null) {
+ if (vr.getSeverity() == IssueSeverity.INFORMATION) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
+ } else if (vr.getSeverity() == IssueSeverity.WARNING) {
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
+ } else {
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
+ }
+ }
+ }
+ }
+ }
+ txTime = txTime + (System.nanoTime() - t);
+ }
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_CodeableConcept"), e.getMessage());
+ }
+ }
+ } else if (binding.hasValueSet()) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_by_URI_reference_cannot_be_checked"));
+ } else if (!noBindingMsgSuppressed) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_for_path__has_no_source_so_cant_be_checked"), path);
+ }
+ }
+ }
+ return res;
+ }
+
+ private boolean checkTerminologyCodeableConcept(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, NodeStack stack, StructureDefinition logical) {
+ boolean res = true;
+ if (!noTerminologyChecks && theElementCntext != null && theElementCntext.hasBinding()) {
+ ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null,messages.getString("Binding_for__missing_cc"), path)) {
+ if (binding.hasValueSet()) {
+ ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(binding.getValueSet()))) {
+ try {
+ CodeableConcept cc = convertToCodeableConcept(element, logical);
+ if (!cc.hasCoding()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("No_code_provided_and_a_code_is_required_from_the_value_set__"), describeReference(binding.getValueSet()), valueset.getUrl());
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("No_code_provided_and_a_code_must_be_provided_from_the_value_set__max_value_set_"), describeReference(ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet")), valueset.getUrl());
+ else
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("No_code_provided_and_a_code_should_be_provided_from_the_value_set__"), describeReference(binding.getValueSet()), valueset.getUrl());
+ }
+ } else {
+ long t = System.nanoTime();
+
+ // Check whether the codes are appropriate for the type of binding we have
+ boolean bindingsOk = true;
+ if (binding.getStrength() != BindingStrength.EXAMPLE) {
+ boolean atLeastOneSystemIsSupported = false;
+ for (Coding nextCoding : cc.getCoding()) {
+ String nextSystem = nextCoding.getSystem();
+ if (isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
+ atLeastOneSystemIsSupported = true;
+ break;
+ }
+ }
+
+ if (!atLeastOneSystemIsSupported && binding.getStrength() == BindingStrength.EXAMPLE) {
+ // ignore this since we can't validate but it doesn't matter..
+ } else {
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset); // we're going to validate the codings directly
+ if (!vr.isOk()) {
+ bindingsOk = false;
+ if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_from_this_value_set_is_required_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
+ else if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set_class__"), describeReference(binding.getValueSet()), vr.getErrorClass().toString());
+ }
+ }
+ } else {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the value set " + describeReference(binding.getValueSet()) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), cc, stack);
+ if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("None_of_the_codes_provided_are_in_the_value_set___and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code_codes__"), describeReference(binding.getValueSet()), valueset.getUrl(), ccSummary(cc));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("None_of_the_codes_provided_are_in_the_value_set___and_a_code_is_recommended_to_come_from_this_value_set_codes__"), describeReference(binding.getValueSet()), valueset.getUrl(), ccSummary(cc));
+ }
+ }
+ }
+ } else if (vr.getMessage() != null) {
+ res = false;
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage());
+ } else {
+ res = false;
+ }
+ }
+ // Then, for any codes that are in code systems we are able
+ // to validate, we'll validate that the codes actually exist
+ if (bindingsOk) {
+ for (Coding nextCoding : cc.getCoding()) {
+ String nextCode = nextCoding.getCode();
+ String nextSystem = nextCoding.getSystem();
+ if (isNotBlank(nextCode) && isNotBlank(nextSystem) && context.supportsSystem(nextSystem)) {
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), nextSystem, nextCode, null);
+ if (!vr.isOk()) {
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Code_0_is_not_a_valid_code_in_code_system_1"), nextCode, nextSystem);
+ }
+ }
+ }
+ }
+ txTime = txTime + (System.nanoTime() - t);
+ }
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_CodeableConcept"), e.getMessage());
+ }
+ // special case: if the logical model has both CodeableConcept and Coding mappings, we'll also check the first coding.
+ if (getMapping("http://hl7.org/fhir/terminology-pattern", logical, logical.getSnapshot().getElementFirstRep()).contains("Coding")) {
+ checkTerminologyCoding(errors, path, element, profile, theElementCntext, true, true, stack, logical);
+ }
+ }
+ } else if (binding.hasValueSet()) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_by_URI_reference_cannot_be_checked"));
+ } else if (!noBindingMsgSuppressed) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_for_path__has_no_source_so_cant_be_checked"), path);
+ }
+ }
+ }
+ return res;
+ }
+
+ private void checkTerminologyCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack, StructureDefinition logical) {
+ Coding c = convertToCoding(element, logical);
+ String code = c.getCode();
+ String system = c.getSystem();
+ String display = c.getDisplay();
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system),messages.getString("Codingsystem_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),messages.getString("The_Coding_references_a_value_set_not_a_code_system_"), system);
+ try {
+ if (checkCode(errors, element, path, code, system, display, checkDisplay, stack))
+ if (theElementCntext != null && theElementCntext.hasBinding()) {
+ ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null,messages.getString("Binding_for__missing"), path)) {
+ if (binding.hasValueSet()) {
+ ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(binding.getValueSet()))) {
+ try {
+ long t = System.nanoTime();
+ ValidationResult vr = null;
+ if (binding.getStrength() != BindingStrength.EXAMPLE) {
+ vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
+ }
+ txTime = txTime + (System.nanoTime() - t);
+ if (vr != null && !vr.isOk()) {
+ if (vr.IsNoService())
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_value_provided_could_not_be_validated_in_the_absence_of_a_terminology_server"));
+ else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_from_this_value_set_is_required"), describeReference(binding.getValueSet(), valueset));
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
+ else if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code"), describeReference(binding.getValueSet(), valueset));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set"), describeReference(binding.getValueSet(), valueset));
+ }
+ }
+ } else if (binding.getStrength() == BindingStrength.REQUIRED)
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is required from this value set" + (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
+ else
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_Coding_provided_is_not_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code"), describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_Coding_provided_is_not_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set"), describeReference(binding.getValueSet(), valueset), (vr.getMessage() != null ? " (error message = " + vr.getMessage() + ")" : ""));
+ }
+ }
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_Coding"), e.getMessage());
+ }
+ }
+ } else if (binding.hasValueSet()) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_by_URI_reference_cannot_be_checked"));
+ } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_for_path__has_no_source_so_cant_be_checked"), path);
+ }
+ }
+ }
+ } catch (Exception e) {
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_Coding_"), e.getMessage(), e.toString());
+ }
+ }
+ }
+
+ private CodeableConcept convertToCodeableConcept(Element element, StructureDefinition logical) {
+ CodeableConcept res = new CodeableConcept();
+ for (ElementDefinition ed : logical.getSnapshot().getElement()) {
+ if (Utilities.charCount(ed.getPath(), '.') == 1) {
+ List maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
+ for (String m : maps) {
+ String name = tail(ed.getPath());
+ List list = new ArrayList<>();
+ element.getNamedChildren(name, list);
+ if (!list.isEmpty()) {
+ if ("Coding.code".equals(m)) {
+ res.getCodingFirstRep().setCode(list.get(0).primitiveValue());
+ } else if ("Coding.system[fmt:OID]".equals(m)) {
+ String oid = list.get(0).primitiveValue();
+ String url = context.oid2Uri(oid);
+ if (url != null) {
+ res.getCodingFirstRep().setSystem(url);
+ } else {
+ res.getCodingFirstRep().setSystem("urn:oid:" + oid);
+ }
+ } else if ("Coding.version".equals(m)) {
+ res.getCodingFirstRep().setVersion(list.get(0).primitiveValue());
+ } else if ("Coding.display".equals(m)) {
+ res.getCodingFirstRep().setDisplay(list.get(0).primitiveValue());
+ } else if ("CodeableConcept.text".equals(m)) {
+ res.setText(list.get(0).primitiveValue());
+ } else if ("CodeableConcept.coding".equals(m)) {
+ StructureDefinition c = context.fetchTypeDefinition(ed.getTypeFirstRep().getCode());
+ for (Element e : list) {
+ res.addCoding(convertToCoding(e, c));
+ }
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ private Coding convertToCoding(Element element, StructureDefinition logical) {
+ Coding res = new Coding();
+ for (ElementDefinition ed : logical.getSnapshot().getElement()) {
+ if (Utilities.charCount(ed.getPath(), '.') == 1) {
+ List maps = getMapping("http://hl7.org/fhir/terminology-pattern", logical, ed);
+ for (String m : maps) {
+ String name = tail(ed.getPath());
+ List list = new ArrayList<>();
+ element.getNamedChildren(name, list);
+ if (!list.isEmpty()) {
+ if ("Coding.code".equals(m)) {
+ res.setCode(list.get(0).primitiveValue());
+ } else if ("Coding.system[fmt:OID]".equals(m)) {
+ String oid = list.get(0).primitiveValue();
+ String url = context.oid2Uri(oid);
+ if (url != null) {
+ res.setSystem(url);
+ } else {
+ res.setSystem("urn:oid:" + oid);
+ }
+ } else if ("Coding.version".equals(m)) {
+ res.setVersion(list.get(0).primitiveValue());
+ } else if ("Coding.display".equals(m)) {
+ res.setDisplay(list.get(0).primitiveValue());
+ }
+ }
+ }
+ }
+ }
+ return res;
+ }
+
+ private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, CodeableConcept cc, NodeStack stack) {
+ // TODO Auto-generated method stub
+ ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(maxVSUrl))) {
+ try {
+ long t = System.nanoTime();
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), cc, valueset);
+ txTime = txTime + (System.nanoTime() - t);
+ if (!vr.isOk()) {
+ if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("None_of_the_codes_provided_could_be_validated_against_the_maximum_value_set___error__"), describeReference(maxVSUrl), valueset.getUrl(), vr.getMessage());
+ else
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "None of the codes provided are in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + ", and a code from this value set is required) (codes = " + ccSummary(cc) + ")");
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_CodeableConcept_using_maxValueSet"), e.getMessage());
+ }
+ }
+ }
+
+ private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, Coding c, NodeStack stack) {
+ // TODO Auto-generated method stub
+ ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(maxVSUrl))) {
+ try {
+ long t = System.nanoTime();
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
+ txTime = txTime + (System.nanoTime() - t);
+ if (!vr.isOk()) {
+ if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_code_provided_could_not_be_validated_against_the_maximum_value_set___error__"), describeReference(maxVSUrl), valueset.getUrl(), vr.getMessage());
+ else
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + ", and a code from this value set is required) (code = " + c.getSystem() + "#" + c.getCode() + ")");
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_CodeableConcept_using_maxValueSet"), e.getMessage());
+ }
+ }
+ }
+
+ private void checkMaxValueSet(List errors, String path, Element element, StructureDefinition profile, String maxVSUrl, String value, NodeStack stack) {
+ // TODO Auto-generated method stub
+ ValueSet valueset = resolveBindingReference(profile, maxVSUrl, profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(maxVSUrl))) {
+ try {
+ long t = System.nanoTime();
+ ValidationResult vr = context.validateCode(new ValidationOptions(stack.workingLang), value, valueset);
+ txTime = txTime + (System.nanoTime() - t);
+ if (!vr.isOk()) {
+ if (vr.getErrorClass() != null && vr.getErrorClass().isInfrastructure())
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_code_provided_could_not_be_validated_against_the_maximum_value_set___error__"), describeReference(maxVSUrl), valueset.getUrl(), vr.getMessage());
+ else
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The code provided is not in the maximum value set " + describeReference(maxVSUrl) + " (" + valueset.getUrl() + "), and a code from this value set is required) (code = " + value + "), (error = " + vr.getMessage() + ")");
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_CodeableConcept_using_maxValueSet"), e.getMessage());
+ }
+ }
+ }
+
+ private String ccSummary(CodeableConcept cc) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (Coding c : cc.getCoding())
+ b.append(c.getSystem() + "#" + c.getCode());
+ return b.toString();
+ }
+
+ private void checkCoding(List errors, String path, Element focus, Coding fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
+ checkFixedValue(errors, path + ".version", focus.getNamedChild("version"), fixed.getVersionElement(), fixedSource, "version", focus, pattern);
+ checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
+ checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), fixedSource, "display", focus, pattern);
+ checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), fixedSource, "userSelected", focus, pattern);
+ }
+
+ private void checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) {
+ String code = element.getNamedChildValue("code");
+ String system = element.getNamedChildValue("system");
+ String display = element.getNamedChildValue("display");
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system),messages.getString("Codingsystem_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),messages.getString("The_Coding_references_a_value_set_not_a_code_system_"), system);
+ try {
+ if (checkCode(errors, element, path, code, system, display, checkDisplay, stack))
+ if (theElementCntext != null && theElementCntext.hasBinding()) {
+ ElementDefinitionBindingComponent binding = theElementCntext.getBinding();
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, binding != null,messages.getString("Binding_for__missing"), path)) {
+ if (binding.hasValueSet()) {
+ ValueSet valueset = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, valueset != null,messages.getString("ValueSet__not_found_by_validator"), describeReference(binding.getValueSet()))) {
+ try {
+ Coding c = ObjectConverter.readAsCoding(element);
+ long t = System.nanoTime();
+ ValidationResult vr = null;
+ if (binding.getStrength() != BindingStrength.EXAMPLE) {
+ vr = context.validateCode(new ValidationOptions(stack.workingLang), c, valueset);
+ }
+ txTime = txTime + (System.nanoTime() - t);
+ if (vr != null && !vr.isOk()) {
+ if (vr.IsNoService())
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_value_provided_could_not_be_validated_in_the_absence_of_a_terminology_server"));
+ else if (vr.getErrorClass() != null && !vr.getErrorClass().isInfrastructure()) {
+ if (binding.getStrength() == BindingStrength.REQUIRED)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_from_this_value_set_is_required"), describeReference(binding.getValueSet(), valueset));
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
+ else if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code"), describeReference(binding.getValueSet(), valueset));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Could_not_confirm_that_the_codes_provided_are_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set"), describeReference(binding.getValueSet(), valueset));
+ }
+ }
+ } else if (binding.getStrength() == BindingStrength.REQUIRED)
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The Coding provided is not in the value set " + describeReference(binding.getValueSet(), valueset) + ", and a code is required from this value set. " + getErrorMessage(vr.getMessage()));
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), c, stack);
+ else
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_Coding_provided_is_not_in_the_value_set__and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code_"), describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_Coding_provided_is_not_in_the_value_set__and_a_code_is_recommended_to_come_from_this_value_set_"), describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()));
+ }
+ }
+ }
+ } catch (Exception e) {
+ warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_Coding"), e.getMessage());
+ }
+ }
+ } else if (binding.hasValueSet()) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_by_URI_reference_cannot_be_checked"));
+ } else if (!inCodeableConcept && !noBindingMsgSuppressed) {
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Binding_for_path__has_no_source_so_cant_be_checked"), path);
+ }
+ }
+ }
+ } catch (Exception e) {
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("Error__validating_Coding_"), e.getMessage(), e.toString());
+ }
+ }
+ }
+
+ 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, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
+ checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
+ checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
+ checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
+
+ }
+
+ private StructureDefinition checkExtension(ValidatorHostContext hostContext, List errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException {
+ String url = element.getNamedChildValue("url");
+ boolean isModifier = element.getName().equals("modifierExtension");
+
+ long t = System.nanoTime();
+ StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
+ sdTime = sdTime + (System.nanoTime() - t);
+ if (ex == null) {
+ if (xverManager == null) {
+ xverManager = new XVerExtensionManager(context);
+ }
+ if (xverManager.matchingUrl(url)) {
+ switch (xverManager.status(url)) {
+ case BadVersion:
+ rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false,messages.getString("Extension_url__is_not_valid_invalidVersion_"), url, xverManager.getVersion(url));
+ break;
+ case Unknown:
+ rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false,messages.getString("Extension_url__is_not_valid_unknown_Element_id_"), url, xverManager.getElementId(url));
+ break;
+ case Invalid:
+ rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false,messages.getString("Extension_url__is_not_valid_Element_id__is_valid_but_cannot_be_used_in_a_crossversion_paradigm_because_there_has_been_no_changes_across_the_relevant_versions"), url, xverManager.getElementId(url));
+ break;
+ case Valid:
+ ex = xverManager.makeDefinition(url);
+ context.generateSnapshot(ex);
+ context.cacheResource(ex);
+ break;
+ default:
+ rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", false,messages.getString("Extension_url__evaluation_state_illegal"), url);
+ break;
+ }
+ } else if (extensionUrl != null && !isAbsolute(url)) {
+ if (extensionUrl.equals(profile.getUrl())) {
+ rule(errors, IssueType.INVALID, element.line(), element.col(), path + "[url='" + url + "']", hasExtensionSlice(profile, url),messages.getString("Subextension_url__is_not_defined_by_the_Extension_"), url, profile.getUrl());
+ }
+ } else if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowUnknownExtension(url),messages.getString("The_extension__is_unknown_and_not_allowed_here"), url)) {
+ hint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, isKnownExtension(url),messages.getString("Unknown_extension_"), url);
+ }
+ }
+ if (ex != null) {
+ trackUsage(ex, hostContext, element);
+ if (def.getIsModifier()) {
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),messages.getString("Extension_modifier_mismatch_the_extension_element_is_labelled_as_a_modifier_but_the_underlying_extension_is_not"));
+ } else {
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),messages.getString("Extension_modifier_mismatch_the_extension_element_is_not_labelled_as_a_modifier_but_the_underlying_extension_is"));
+ }
+ // two questions
+ // 1. can this extension be used here?
+ checkExtensionContext(errors, resource, container, ex, containerStack, hostContext);
+
+ if (isModifier)
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(),messages.getString("The_Extension__must_be_used_as_a_modifierExtension"), url);
+ else
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(),messages.getString("The_Extension__must_not_be_used_as_an_extension_its_a_modifierExtension"), url);
+
+ // check the type of the extension:
+ Set allowedTypes = listExtensionTypes(ex);
+ String actualType = getExtensionType(element);
+ if (actualType == null)
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.isEmpty(),messages.getString("The_Extension__definition_is_for_a_simple_extension_so_it_must_contain_a_value_not_extensions"), url);
+ else
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, allowedTypes.contains(actualType),messages.getString("The_Extension__definition_allows_for_the_types__but_found_type_"), url, allowedTypes.toString(), actualType);
+
+ // 3. is the content of the extension valid?
+ validateElement(hostContext, errors, ex, ex.getSnapshot().getElement().get(0), null, null, resource, element, "Extension", stack, false, true, url);
+
+ }
+ return ex;
+ }
+
+ private boolean hasExtensionSlice(StructureDefinition profile, String sliceName) {
+ for (ElementDefinition ed : profile.getSnapshot().getElement()) {
+ if (ed.getPath().equals("Extension.extension.url") && ed.hasFixed() && sliceName.equals(ed.getFixed().primitiveValue())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private String getExtensionType(Element element) {
+ for (Element e : element.getChildren()) {
+ if (e.getName().startsWith("value")) {
+ String tn = e.getName().substring(5);
+ String ltn = Utilities.uncapitalize(tn);
+ if (isPrimitiveType(ltn))
+ return ltn;
else
- ft = tryParse(ref);
+ return tn;
+ }
+ }
+ return null;
+ }
- if (reference.hasType()) { // R4 onwards...
- // the type has to match the specified
- String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType();
- TypeRefComponent containerType = container.getType("Reference");
- if (!containerType.hasTargetProfile(tu) && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
- boolean matchingResource = false;
- for (CanonicalType target : containerType.getTargetProfile()) {
- StructureDefinition sd = resolveProfile(profile, target.asStringValue());
- if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) {
- matchingResource = true;
- break;
- }
- }
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource,
- "The type '" + reference.getType() + "' is not a valid Target for this element (must be one of " + container.getType("Reference").getTargetProfile() + ")");
+ private Set listExtensionTypes(StructureDefinition ex) {
+ ElementDefinition vd = null;
+ for (ElementDefinition ed : ex.getSnapshot().getElement()) {
+ if (ed.getPath().startsWith("Extension.value")) {
+ vd = ed;
+ break;
+ }
+ }
+ Set res = new HashSet();
+ if (vd != null && !"0".equals(vd.getMax())) {
+ for (TypeRefComponent tr : vd.getType()) {
+ res.add(tr.getWorkingCode());
+ }
+ }
+ return res;
+ }
- }
- // the type has to match the actual
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft == null || ft.equals(reference.getType()), "The specified type '" + reference.getType() + "' does not match the found type '" + ft + "'");
- }
+ private boolean checkExtensionContext(List errors, Element resource, Element container, StructureDefinition definition, NodeStack stack, ValidatorHostContext hostContext) {
+ String extUrl = definition.getUrl();
+ boolean ok = false;
+ CommaSeparatedStringBuilder contexts = new CommaSeparatedStringBuilder();
+ List plist = new ArrayList<>();
+ plist.add(stripIndexes(stack.getLiteralPath()));
+ for (String s : stack.getLogicalPaths()) {
+ String p = stripIndexes(s);
+ // all extensions are always allowed in ElementDefinition.example.value, and in fixed and pattern values. TODO: determine the logical paths from the path stated in the element definition....
+ if (Utilities.existsInList(p, "ElementDefinition.example.value", "ElementDefinition.pattern", "ElementDefinition.fixed")) {
+ return true;
+ }
+ plist.add(p);
- if (we != null && pol.checkType()) {
- if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null, "Unable to determine type of target resource")) {
- // we validate as much as we can. First, can we infer a type from the profile?
- boolean ok = false;
- TypeRefComponent type = getReferenceTypeRef(container.getType());
- if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
- Set types = new HashSet<>();
- List profiles = new ArrayList<>();
- for (UriType u : type.getTargetProfile()) {
- StructureDefinition sd = resolveProfile(profile, u.getValue());
- if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null, "Unable to resolve the profile reference '" + u.getValue() + "'")) {
- types.add(sd.getType());
- if (ft.equals(sd.getType())) {
- ok = true;
- profiles.add(sd);
- }
- }
- }
- if (!pol.checkValid()) {
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0, "Unable to find matching profile for " + ref + " (by type) among choices: " + StringUtils.join("; ", type.getTargetProfile()));
- } else {
- Map> badProfiles = new HashMap>();
- Map> goodProfiles = new HashMap>();
- int goodCount = 0;
- for (StructureDefinition pr : profiles) {
- List profileErrors = new ArrayList();
- validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr, IdStatus.OPTIONAL, we.getStack());
- if (!hasErrors(profileErrors)) {
- goodCount++;
- goodProfiles.put(pr, profileErrors);
- trackUsage(pr, hostContext, element);
- } else {
- badProfiles.put(pr, profileErrors);
- }
- }
- if (goodCount == 1) {
- if (showMessagesFromReferences) {
- for (ValidationMessage vm : goodProfiles.values().iterator().next()) {
- if (!errors.contains(vm)) {
- errors.add(vm);
- }
- }
- }
-
- } else if (goodProfiles.size() == 0) {
- if (!isShowMessagesFromReferences()) {
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), "Unable to find matching profile for " + ref + " among choices: " + asList(type.getTargetProfile()));
- for (StructureDefinition sd : badProfiles.keySet()) {
- slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for " + ref + " matching against Profile" + sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
- }
- } else {
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1, "Unable to find matching profile for " + ref + " among choices: " + asList(type.getTargetProfile()));
- for (List messages : badProfiles.values()) {
- for (ValidationMessage vm : messages) {
- if (!errors.contains(vm)) {
- errors.add(vm);
- }
- }
- }
- }
- } else {
- if (!isShowMessagesFromReferences()) {
- warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for " + ref + " among choices: " + asListByUrl(goodProfiles.keySet()));
- for (StructureDefinition sd : badProfiles.keySet()) {
- slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for " + ref + " matching against Profile" + sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
- }
- } else {
- warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Found multiple matching profiles for " + ref + " among choices: " + asListByUrl(goodProfiles.keySet()));
- for (List messages : goodProfiles.values()) {
- for (ValidationMessage vm : messages) {
- if (!errors.contains(vm)) {
- errors.add(vm);
- }
- }
- }
- }
- }
- }
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok, "Invalid Resource target type. Found " + ft + ", but expected one of (" + types.toString() + ")");
- }
- if (type.hasAggregation()) {
- boolean modeOk = false;
- for (Enumeration mode : type.getAggregation()) {
- if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
- modeOk = true;
- else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
- modeOk = true;
- else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote")))
- modeOk = true;
- }
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk, "Reference is " + refType + " which isn't supported by the specified aggregation mode(s) for the reference");
- }
- }
- }
- if (we == null) {
- TypeRefComponent type = getReferenceTypeRef(container.getType());
- boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
- rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef, "Bundled or contained reference not found within the bundle/resource " + ref);
- }
- if (we == null && ft != null && assumeValidRestReferences) {
- // if we == null, we inferred ft from the reference. if we are told to treat this as gospel
- TypeRefComponent type = getReferenceTypeRef(container.getType());
- Set types = new HashSet<>();
- for (CanonicalType tp : type.getTargetProfile()) {
- StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue());
- if (sd != null) {
- types.add(sd.getType());
- }
- }
- rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || types.contains(ft), "The type '" + ft + "' implied by the reference URL " + ref + " is not a valid Target for this element (must be one of " + types + ")");
-
- }
- if (pol == ReferenceValidationPolicy.CHECK_VALID) {
- // todo....
- }
}
- private String asListByUrl(Collection list) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (StructureDefinition sd : list) {
- b.append(sd.getUrl());
+ for (StructureDefinitionContextComponent ctxt : fixContexts(extUrl, definition.getContext())) {
+ if (ok) {
+ break;
+ }
+ if (ctxt.getType() == ExtensionContextType.ELEMENT) {
+ String en = ctxt.getExpression();
+ contexts.append("e:" + en);
+ if ("Element".equals(en)) {
+ ok = true;
+ } else if (en.equals("Resource") && container.isResource()) {
+ ok = true;
}
- return b.toString();
- }
-
- private String asList(Collection list) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (CanonicalType c : list) {
- b.append(c.getValue());
- }
- return b.toString();
- }
-
- private boolean areAllBaseProfiles(List profiles) {
- for (StructureDefinition sd : profiles) {
- if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
- return false;
+ for (String p : plist) {
+ if (ok) {
+ break;
+ }
+ if (p.equals(en)) {
+ ok = true;
+ } else {
+ String pn = p;
+ String pt = "";
+ if (p.contains(".")) {
+ pn = p.substring(0, p.indexOf("."));
+ pt = p.substring(p.indexOf("."));
}
+ StructureDefinition sd = context.fetchTypeDefinition(pn);
+ while (sd != null) {
+ if ((sd.getType() + pt).equals(en)) {
+ ok = true;
+ break;
+ }
+ if (sd.getBaseDefinition() != null) {
+ sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
+ } else {
+ sd = null;
+ }
+ }
+ }
}
+ } else if (ctxt.getType() == ExtensionContextType.EXTENSION) {
+ contexts.append("x:" + ctxt.getExpression());
+ NodeStack estack = stack.parent;
+ if (estack != null && estack.getElement().fhirType().equals("Extension")) {
+ String ext = estack.element.getNamedChildValue("url");
+ if (ctxt.getExpression().equals(ext)) {
+ ok = true;
+ }
+ }
+ } else if (ctxt.getType() == ExtensionContextType.FHIRPATH) {
+ contexts.append("p:" + ctxt.getExpression());
+ // The context is all elements that match the FHIRPath query found in the expression.
+ List res = fpe.evaluate(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(ctxt.getExpression()));
+ if (res.contains(container)) {
+ ok = true;
+ }
+ } else {
+ throw new Error("Unrecognised extension context " + ctxt.getTypeElement().asStringValue());
+ }
+ }
+ if (!ok) {
+ rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, false,messages.getString("The_extension__is_not_allowed_to_be_used_at_this_point_allowed___this_element_is_"), extUrl, contexts.toString(), plist.toString());
+ return false;
+ } else {
+ if (definition.hasContextInvariant()) {
+ for (StringType s : definition.getContextInvariant()) {
+ if (!fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), container, fpe.parse(s.getValue()))) {
+ rule(errors, IssueType.STRUCTURE, container.line(), container.col(), stack.literalPath, false,messages.getString("The_extension__is_not_allowed_to_be_used_at_this_point_based_on_context_invariant_"), extUrl, s.getValue());
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ private List fixContexts(String extUrl, List list) {
+ List res = new ArrayList<>();
+ for (StructureDefinitionContextComponent ctxt : list) {
+ res.add(ctxt.copy());
+ }
+ if ("http://hl7.org/fhir/StructureDefinition/structuredefinition-fhir-type".equals(extUrl)) {
+ list.get(0).setExpression("ElementDefinition.type");
+ }
+ if ("http://hl7.org/fhir/StructureDefinition/regex".equals(extUrl)) {
+ list.get(1).setExpression("ElementDefinition.type");
+ }
+ return list;
+ }
+
+ private String stripIndexes(String path) {
+ boolean skip = false;
+ StringBuilder b = new StringBuilder();
+ for (char c : path.toCharArray()) {
+ if (skip) {
+ if (c == ']') {
+ skip = false;
+ }
+ } else if (c == '[') {
+ skip = true;
+ } else {
+ b.append(c);
+ }
+ }
+ return b.toString();
+ }
+
+ private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent) {
+ checkFixedValue(errors, path, focus, fixed, fixedSource, propName, parent, false);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern) {
+ if ((fixed == null || fixed.isEmpty()) && focus == null) {
+ ; // this is all good
+ } else if ((fixed == null || fixed.isEmpty()) && focus != null) {
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern,messages.getString("The_element__is_present_in_the_instance_but_not_allowed_in_the_applicable__specified_in_profile"), focus.getName(), (pattern ? "pattern" : "fixed value"));
+ } else if (fixed != null && !fixed.isEmpty() && focus == null) {
+ rule(errors, IssueType.VALUE, parent == null ? -1 : parent.line(), parent == null ? -1 : parent.col(), path, false,messages.getString("Missing_element___required_by_fixed_value_assigned_in_profile_"), propName, fixedSource);
+ } else {
+ String value = focus.primitiveValue();
+ if (fixed instanceof org.hl7.fhir.r5.model.BooleanType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.BooleanType) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.IntegerType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.IntegerType) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.DecimalType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.DecimalType) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.Base64BinaryType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.Base64BinaryType) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.InstantType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.InstantType) fixed).getValue().toString(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.InstantType) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.CodeType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.CodeType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.CodeType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.Enumeration)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.Enumeration) fixed).asStringValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.StringType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.StringType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.StringType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.UriType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UriType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.UriType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.DateType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateType) fixed).getValue().toString(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.DateType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.DateTimeType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue().toString(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.DateTimeType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.OidType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.OidType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.OidType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.UuidType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.UuidType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.UuidType) fixed).getValue());
+ else if (fixed instanceof org.hl7.fhir.r5.model.IdType)
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, check(((org.hl7.fhir.r5.model.IdType) fixed).getValue(), value),messages.getString("Value_is__but_must_be_"), value, ((org.hl7.fhir.r5.model.IdType) fixed).getValue());
+ else if (fixed instanceof Quantity)
+ checkQuantity(errors, path, focus, (Quantity) fixed, fixedSource, pattern);
+ else if (fixed instanceof Address)
+ checkAddress(errors, path, focus, (Address) fixed, fixedSource, pattern);
+ else if (fixed instanceof ContactPoint)
+ checkContactPoint(errors, path, focus, (ContactPoint) fixed, fixedSource, pattern);
+ else if (fixed instanceof Attachment)
+ checkAttachment(errors, path, focus, (Attachment) fixed, fixedSource, pattern);
+ else if (fixed instanceof Identifier)
+ checkIdentifier(errors, path, focus, (Identifier) fixed, fixedSource, pattern);
+ else if (fixed instanceof Coding)
+ checkCoding(errors, path, focus, (Coding) fixed, fixedSource, pattern);
+ else if (fixed instanceof HumanName)
+ checkHumanName(errors, path, focus, (HumanName) fixed, fixedSource, pattern);
+ else if (fixed instanceof CodeableConcept)
+ checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, fixedSource, pattern);
+ else if (fixed instanceof Timing)
+ checkTiming(errors, path, focus, (Timing) fixed, fixedSource, pattern);
+ else if (fixed instanceof Period)
+ checkPeriod(errors, path, focus, (Period) fixed, fixedSource, pattern);
+ else if (fixed instanceof Range)
+ checkRange(errors, path, focus, (Range) fixed, fixedSource, pattern);
+ else if (fixed instanceof Ratio)
+ checkRatio(errors, path, focus, (Ratio) fixed, fixedSource, pattern);
+ else if (fixed instanceof SampledData)
+ checkSampledData(errors, path, focus, (SampledData) fixed, fixedSource, pattern);
+
+ else
+ rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false,messages.getString("Unhandled_fixed_value_type_"), fixed.getClass().getName());
+ List extensions = new ArrayList();
+ focus.getNamedChildren("extension", extensions);
+ if (fixed.getExtension().size() == 0) {
+ rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == 0,messages.getString("No_extensions_allowed_as_the_specified_fixed_value_doesnt_contain_any_extensions"));
+ } else if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, extensions.size() == fixed.getExtension().size(),messages.getString("Extensions_count_mismatch_expected__but_found_"), Integer.toString(fixed.getExtension().size()), Integer.toString(extensions.size()))) {
+ for (Extension e : fixed.getExtension()) {
+ Element ex = getExtensionByUrl(extensions, e.getUrl());
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null,messages.getString("Extension_count_mismatch_unable_to_find_extension_"), e.getUrl())) {
+ checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"));
+ }
+ }
+ }
+ }
+ }
+
+ private void checkHumanName(List errors, String path, Element focus, HumanName fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
+ checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), fixedSource, "text", focus, pattern);
+ checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
+
+ List parts = new ArrayList();
+ focus.getNamedChildren("family", parts);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(),messages.getString("Expected__but_found__family_elements"), (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) {
+ for (int i = 0; i < parts.size(); i++)
+ checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern);
+ }
+ focus.getNamedChildren("given", parts);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(),messages.getString("Expected__but_found__given_elements"), Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) {
+ for (int i = 0; i < parts.size(); i++)
+ checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern);
+ }
+ focus.getNamedChildren("prefix", parts);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(),messages.getString("Expected__but_found__prefix_elements"), Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) {
+ for (int i = 0; i < parts.size(); i++)
+ checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern);
+ }
+ focus.getNamedChildren("suffix", parts);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(),messages.getString("Expected__but_found__suffix_elements"), Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) {
+ for (int i = 0; i < parts.size(); i++)
+ checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern);
+ }
+ }
+
+ private void checkIdentifier(List errors, String path, Element element, ElementDefinition context) {
+ String system = element.getNamedChildValue("system");
+ rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, isAbsolute(system),messages.getString("Identifiersystem_must_be_an_absolute_reference_not_a_local_reference"));
+ }
+
+ private void checkIdentifier(List errors, String path, Element focus, Identifier fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), fixedSource, "use", focus, pattern);
+ checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), fixedSource, "type", focus, pattern);
+ checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
+ checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
+ checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
+ checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), fixedSource, "assigner", focus, pattern);
+ }
+
+ private void checkPeriod(List errors, String path, Element focus, Period fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), fixedSource, "start", focus, pattern);
+ checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), fixedSource, "end", focus, pattern);
+ }
+
+ private void checkPrimitive(Object appContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException {
+ if (isBlank(e.primitiveValue())) {
+ if (e.primitiveValue() == null)
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(),messages.getString("Primitive_types_must_have_a_value_or_must_have_child_extensions"));
+ else if (e.primitiveValue().length() == 0)
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(),messages.getString("Primitive_types_must_have_a_value_that_is_not_empty"));
+ else if (StringUtils.isWhitespace(e.primitiveValue()))
+ warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.hasChildren(),messages.getString("Primitive_types_should_not_only_be_whitespace"));
+ return;
+ }
+ String regex = context.getExtensionString(ToolingExtensions.EXT_REGEX);
+ if (regex != null)
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex),messages.getString("Element_value__does_not_meet_regex_"), e.primitiveValue(), regex);
+
+ if (type.equals("boolean")) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, "true".equals(e.primitiveValue()) || "false".equals(e.primitiveValue()),messages.getString("boolean_values_must_be_true_or_false"));
+ }
+ if (type.equals("uri") || type.equals("oid") || type.equals("uuid") || type.equals("url") || type.equals("canonical")) {
+ String url = e.primitiveValue();
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"),messages.getString("URI_values_cannot_start_with_oid"));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"),messages.getString("URI_values_cannot_start_with_uuid"));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", "")) // work around an old invalid example in a core package || "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url),messages.getString("URI_values_cannot_have_whitespace"), url);
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_length_of_"), context.getMaxLength());
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_length_of_"), context.getMaxLength());
+
+ if (type.equals("oid")) {
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:oid:"),messages.getString("OIDs_must_start_with_urnoid")))
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isOid(url.substring(8)),messages.getString("OIDs_must_be_valid"));
+ }
+ if (type.equals("uuid")) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, url.startsWith("urn:uuid:"),messages.getString("UUIDs_must_start_with_urnuuid"));
+ try {
+ UUID.fromString(url.substring(8));
+ } catch (Exception ex) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("UUIDs_must_be_valid_"), ex.getMessage());
+ }
+ }
+
+ // now, do we check the URI target?
+ if (fetcher != null) {
+ boolean found;
+ try {
+ found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url);
+ } catch (IOException e1) {
+ found = false;
+ }
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, found,messages.getString("URL_value__does_not_resolve"), url);
+ }
+ }
+ if (type.equals("id")) {
+ // work around an old issue with ElementDefinition.id
+ if (!context.getPath().equals("ElementDefinition.id") && !VersionUtilities.versionsCompatible("1.4", this.context.getVersion())) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.isValidId(e.primitiveValue()),messages.getString("id_value__is_not_valid"), e.primitiveValue());
+ }
+ }
+ if (type.equalsIgnoreCase("string") && e.hasPrimitiveValue()) {
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().length() > 0,messages.getString("value_cannot_be_empty"))) {
+ warning(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() == null || e.primitiveValue().trim().equals(e.primitiveValue()),messages.getString("value_should_not_start_or_finish_with_whitespace"));
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().length() <= 1048576,messages.getString("value_is_longer_than_permitted_maximum_length_of_1_MB_1048576_bytes"))) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_length_of_"), context.getMaxLength());
+ }
+ }
+ }
+ if (type.equals("dateTime")) {
+ warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()),messages.getString("The_value__is_outside_the_range_of_reasonable_years__check_for_data_entry_error"), e.primitiveValue());
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() .matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"),messages.getString("Not_a_valid_date_time"));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()),messages.getString("if_a_date_has_a_time_it_must_have_a_timezone"));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_length_of_"), context.getMaxLength());
+ try {
+ DateTimeType dt = new DateTimeType(e.primitiveValue());
+ } catch (Exception ex) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("Not_a_valid_datetime_"), ex.getMessage());
+ }
+ }
+ if (type.equals("time")) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue() .matches("([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)"),messages.getString("Not_a_valid_time"));
+ try {
+ TimeType dt = new TimeType(e.primitiveValue());
+ } catch (Exception ex) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("Not_a_valid_time_"), ex.getMessage());
+ }
+ }
+ if (type.equals("date")) {
+ warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()),messages.getString("The_value__is_outside_the_range_of_reasonable_years__check_for_data_entry_error"), e.primitiveValue());
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1]))?)?"),messages.getString("Not_a_valid_date"));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_value_of_"), context.getMaxLength());
+ try {
+ DateType dt = new DateType(e.primitiveValue());
+ } catch (Exception ex) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("Not_a_valid_date_"), ex.getMessage());
+ }
+ }
+ if (type.equals("base64Binary")) {
+ String encoded = e.primitiveValue();
+ if (isNotBlank(encoded)) {
+ /*
+ * Technically this is not bulletproof as some invalid base64 won't be caught,
+ * but I think it's good enough. The original code used Java8 Base64 decoder
+ * but I've replaced it with a regex for 2 reasons:
+ * 1. This code will run on any version of Java
+ * 2. This code doesn't actually decode, which is much easier on memory use for big payloads
+ */
+ int charCount = 0;
+ for (int i = 0; i < encoded.length(); i++) {
+ char nextChar = encoded.charAt(i);
+ if (Character.isWhitespace(nextChar)) {
+ continue;
+ }
+ if (Character.isLetterOrDigit(nextChar)) {
+ charCount++;
+ }
+ if (nextChar == '/' || nextChar == '=' || nextChar == '+') {
+ charCount++;
+ }
+ }
+
+ if (charCount > 0 && charCount % 4 != 0) {
+ String value = encoded.length() < 100 ? encoded : "(snip)";
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("The_value_0_is_not_a_valid_Base64_value"), value);
+ }
+ }
+ }
+ if (type.equals("integer") || type.equals("unsignedInt") || type.equals("positiveInt")) {
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isInteger(e.primitiveValue()),messages.getString("The_value__is_not_a_valid_integer"), e.primitiveValue())) {
+ Integer v = new Integer(e.getValue()).intValue();
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || !context.getMaxValueIntegerType().hasValue() || (context.getMaxValueIntegerType().getValue() >= v),messages.getString("value_is_greater_than_permitted_maximum_value_of_"), (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : ""));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || !context.getMinValueIntegerType().hasValue() || (context.getMinValueIntegerType().getValue() <= v),messages.getString("value_is_less_than_permitted_minimum_value_of_"), (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : ""));
+ if (type.equals("unsignedInt"))
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0,messages.getString("value_is_less_than_permitted_minimum_value_of_0"));
+ if (type.equals("positiveInt"))
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0,messages.getString("value_is_less_than_permitted_minimum_value_of_1"));
+ }
+ }
+ if (type.equals("integer64")) {
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isLong(e.primitiveValue()),messages.getString("The_value__is_not_a_valid_integer64"), e.primitiveValue())) {
+ Long v = new Long(e.getValue()).longValue();
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueInteger64Type() || !context.getMaxValueInteger64Type().hasValue() || (context.getMaxValueInteger64Type().getValue() >= v),messages.getString("value_is_greater_than_permitted_maximum_value_of_"), (context.hasMaxValueInteger64Type() ? context.getMaxValueInteger64Type() : ""));
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueInteger64Type() || !context.getMinValueInteger64Type().hasValue() || (context.getMinValueInteger64Type().getValue() <= v),messages.getString("value_is_less_than_permitted_minimum_value_of_"), (context.hasMinValueInteger64Type() ? context.getMinValueInteger64Type() : ""));
+ if (type.equals("unsignedInt"))
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, v >= 0,messages.getString("value_is_less_than_permitted_minimum_value_of_0"));
+ if (type.equals("positiveInt"))
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, v > 0,messages.getString("value_is_less_than_permitted_minimum_value_of_1"));
+ }
+ }
+ if (type.equals("decimal")) {
+ if (e.primitiveValue() != null) {
+ DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false);
+ if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE,messages.getString("The_value__is_not_a_valid_decimal"), e.primitiveValue()))
+ warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE,messages.getString("The_value__is_outside_the_range_of_commonlyreasonably_supported_decimals"), e.primitiveValue());
+ }
+ }
+ if (type.equals("instant")) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches("-?[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))"),messages.getString("The_instant__is_not_valid_by_regex"), e.primitiveValue());
+ warning(errors, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()),messages.getString("The_value__is_outside_the_range_of_reasonable_years__check_for_data_entry_error"), e.primitiveValue());
+ try {
+ InstantType dt = new InstantType(e.primitiveValue());
+ } catch (Exception ex) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("Not_a_valid_instant_"), ex.getMessage());
+ }
+ }
+
+ if (type.equals("code") && e.primitiveValue() != null) {
+ // Technically, a code is restricted to string which has at least one character and no leading or trailing whitespace, and where there is no whitespace
+ // other than single spaces in the contents
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, passesCodeWhitespaceRules(e.primitiveValue()),messages.getString("The_code__is_not_valid_whitespace_rules"), e.primitiveValue());
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(),messages.getString("value_is_longer_than_permitted_maximum_length_of_"), context.getMaxLength());
+ }
+
+ if (context.hasBinding() && e.primitiveValue() != null) {
+ checkPrimitiveBinding(errors, path, type, context, e, profile, node);
+ }
+
+ if (type.equals("xhtml")) {
+ XhtmlNode xhtml = e.getXhtml();
+ if (xhtml != null) { // if it is null, this is an error already noted in the parsers
+ // check that the namespace is there and correct.
+ String ns = xhtml.getNsDecl();
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, FormatUtilities.XHTML_NS.equals(ns),messages.getString("Wrong_namespace_on_the_XHTML__should_be_"), ns, FormatUtilities.XHTML_NS);
+ // check that inner namespaces are all correct
+ checkInnerNS(errors, e, path, xhtml.getChildNodes());
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()),messages.getString("Wrong_name_on_the_XHTML___must_start_with_div"), ns);
+ // check that no illegal elements and attributes have been used
+ checkInnerNames(errors, e, path, xhtml.getChildNodes());
+ }
+ }
+
+ if (context.hasFixed()) {
+ checkFixedValue(errors, path, e, context.getFixed(), profile.getUrl(), context.getSliceName(), null, false);
+ }
+ if (context.hasPattern()) {
+ checkFixedValue(errors, path, e, context.getPattern(), profile.getUrl(), context.getSliceName(), null, true);
+ }
+
+ // for nothing to check
+ }
+
+ private boolean isDefinitionURL(String url) {
+ return Utilities.existsInList(url, "http://hl7.org/fhirpath/System.Boolean", "http://hl7.org/fhirpath/System.String", "http://hl7.org/fhirpath/System.Integer",
+ "http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity");
+ }
+
+ private void checkInnerNames(List errors, Element e, String path, List list) {
+ for (XhtmlNode node : list) {
+ if (node.getNodeType() == NodeType.Element) {
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.existsInList(node.getName(), "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong", "small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup", "ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td", "code", "samp", "img", "map", "area" ),messages.getString("Illegal_element_name_in_the_XHTML_"), node.getName());
+ for (String an : node.getAttributes().keySet()) {
+ boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
+ "title", "style", "class", "id", "lang", "xml:lang", "dir", "accesskey", "tabindex",
+ // tables
+ "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
+
+ Utilities.existsInList(node.getName() + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
+ "a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
+ "img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
+ "area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
+ "table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
+ );
+ if (!ok)
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, false,messages.getString("Illegal_attribute_name_in_the_XHTML__on_"), an, node.getName());
+ }
+ checkInnerNames(errors, e, path, node.getChildNodes());
+ }
+ }
+ }
+
+ private void checkInnerNS(List errors, Element e, String path, List list) {
+ for (XhtmlNode node : list) {
+ if (node.getNodeType() == NodeType.Element) {
+ String ns = node.getNsDecl();
+ rule(errors, IssueType.INVALID, e.line(), e.col(), path, ns == null || FormatUtilities.XHTML_NS.equals(ns),messages.getString("Wrong_namespace_on_the_XHTML__should_be_"), ns, FormatUtilities.XHTML_NS);
+ checkInnerNS(errors, e, path, node.getChildNodes());
+ }
+ }
+ }
+
+ private void checkPrimitiveBinding(List errors, String path, String type, ElementDefinition elementContext, Element element, StructureDefinition profile, NodeStack stack) {
+ // We ignore bindings that aren't on string, uri or code
+ if (!element.hasPrimitiveValue() || !("code".equals(type) || "string".equals(type) || "uri".equals(type) || "url".equals(type) || "canonical".equals(type))) {
+ return;
+ }
+ if (noTerminologyChecks)
+ return;
+
+ String value = element.primitiveValue();
+ // System.out.println("check "+value+" in "+path);
+
+ // firstly, resolve the value set
+ ElementDefinitionBindingComponent binding = elementContext.getBinding();
+ if (binding.hasValueSet()) {
+ ValueSet vs = resolveBindingReference(profile, binding.getValueSet(), profile.getUrl());
+ if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null,messages.getString("ValueSet_0_not_found_by_validator"), describeReference(binding.getValueSet()))) {
+ long t = System.nanoTime();
+ ValidationResult vr = null;
+ if (binding.getStrength() != BindingStrength.EXAMPLE) {
+ vr = context.validateCode(new ValidationOptions(stack.workingLang), value, vs);
+ }
+ txTime = txTime + (System.nanoTime() - t);
+ if (vr != null && !vr.isOk()) {
+ if (vr.IsNoService())
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_value_provided__could_not_be_validated_in_the_absence_of_a_terminology_server"), value);
+ else if (binding.getStrength() == BindingStrength.REQUIRED)
+ txRule(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, "The value provided ('" + value + "') is not in the value set " + describeReference(binding.getValueSet()) + " (" + vs.getUrl() + ", and a code is required from this value set)" + getErrorMessage(vr.getMessage()));
+ else if (binding.getStrength() == BindingStrength.EXTENSIBLE) {
+ if (binding.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"))
+ checkMaxValueSet(errors, path, element, profile, ToolingExtensions.readStringExtension(binding, "http://hl7.org/fhir/StructureDefinition/elementdefinition-maxValueSet"), value, stack);
+ else if (!noExtensibleWarnings)
+ txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_value_provided__is_not_in_the_value_set___and_a_code_should_come_from_this_value_set_unless_it_has_no_suitable_code"), value, describeReference(binding.getValueSet()), vs.getUrl(), getErrorMessage(vr.getMessage()));
+ } else if (binding.getStrength() == BindingStrength.PREFERRED) {
+ if (baseOnly) {
+ txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false,messages.getString("The_value_provided__is_not_in_the_value_set___and_a_code_is_recommended_to_come_from_this_value_set"), value, describeReference(binding.getValueSet()), vs.getUrl(), getErrorMessage(vr.getMessage()));
+ }
+ }
+ }
+ }
+ } else if (!noBindingMsgSuppressed)
+ hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, !type.equals("code"),messages.getString("Binding_has_no_source_so_cant_be_checked"));
+ }
+
+ private void checkQuantity(List errors, String path, Element focus, Quantity fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), fixedSource, "value", focus, pattern);
+ checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), fixedSource, "comparator", focus, pattern);
+ checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), fixedSource, "units", focus, pattern);
+ checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), fixedSource, "system", focus, pattern);
+ checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern);
+ }
+
+ // implementation
+
+ private void checkRange(List errors, String path, Element focus, Range fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), fixedSource, "low", focus, pattern);
+ checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), fixedSource, "high", focus, pattern);
+
+ }
+
+ private void checkRatio(List errors, String path, Element focus, Ratio fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), fixedSource, "numerator", focus, pattern);
+ checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), fixedSource, "denominator", focus, pattern);
+ }
+
+ private void checkReference(ValidatorHostContext hostContext, List errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException {
+ Reference reference = ObjectConverter.readAsReference(element);
+
+ String ref = reference.getReference();
+ if (Utilities.noString(ref)) {
+ if (Utilities.noString(reference.getIdentifier().getSystem()) && Utilities.noString(reference.getIdentifier().getValue())) {
+ warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, !Utilities.noString(element.getNamedChildValue("display")),messages.getString("A_Reference_without_an_actual_reference_or_identifier_should_have_a_display"));
+ }
+ return;
+ } else if (Utilities.existsInList(ref, "http://tools.ietf.org/html/bcp47")) {
+ // special known URLs that can't be validated but are known to be valid
+ return;
+ }
+
+ ResolvedReference we = localResolve(ref, stack, errors, path, (Element) hostContext.getAppContext(), element);
+ String refType;
+ if (ref.startsWith("#")) {
+ refType = "contained";
+ } else {
+ if (we == null) {
+ refType = "remote";
+ } else {
+ refType = "bundled";
+ }
+ }
+ ReferenceValidationPolicy pol = refType.equals("contained") || refType.equals("bundled") ? ReferenceValidationPolicy.CHECK_VALID : fetcher == null ? ReferenceValidationPolicy.IGNORE : fetcher.validationPolicy(hostContext.getAppContext(), path, ref);
+
+ if (pol.checkExists()) {
+ if (we == null) {
+ if (fetcher == null) {
+ if (!refType.equals("contained"))
+ throw new FHIRException("Resource resolution services not provided");
+ } else {
+ Element ext = null;
+ if (fetchCache.containsKey(ref)) {
+ ext = fetchCache.get(ref);
+ } else {
+ try {
+ ext = fetcher.fetch(hostContext.getAppContext(), ref);
+ } catch (IOException e) {
+ throw new FHIRException(e);
+ }
+ if (ext != null) {
+ fetchCache.put(ref, ext);
+ }
+ }
+ we = ext == null ? null : makeExternalRef(ext, path);
+ }
+ }
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, (allowExamples && (ref.contains("example.org") || ref.contains("acme.com"))) || (we != null || pol == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS),messages.getString("Unable_to_resolve_resource_"), ref);
+ }
+
+ String ft;
+ if (we != null)
+ ft = we.getType();
+ else
+ ft = tryParse(ref);
+
+ if (reference.hasType()) { // R4 onwards...
+ // the type has to match the specified
+ String tu = isAbsolute(reference.getType()) ? reference.getType() : "http://hl7.org/fhir/StructureDefinition/" + reference.getType();
+ TypeRefComponent containerType = container.getType("Reference");
+ if (!containerType.hasTargetProfile(tu) && !containerType.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
+ boolean matchingResource = false;
+ for (CanonicalType target : containerType.getTargetProfile()) {
+ StructureDefinition sd = resolveProfile(profile, target.asStringValue());
+ if (("http://hl7.org/fhir/StructureDefinition/" + sd.getType()).equals(tu)) {
+ matchingResource = true;
+ break;
+ }
+ }
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, matchingResource,messages.getString("The_type__is_not_a_valid_Target_for_this_element_must_be_one_of_"), reference.getType(), container.getType("Reference").getTargetProfile());
+
+ }
+ // the type has to match the actual
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft == null || ft.equals(reference.getType()),messages.getString("The_specified_type__does_not_match_the_found_type_"), reference.getType(), ft);
+ }
+
+ if (we != null && pol.checkType()) {
+ if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ft != null,messages.getString("Unable_to_determine_type_of_target_resource"))) {
+ // we validate as much as we can. First, can we infer a type from the profile?
+ boolean ok = false;
+ TypeRefComponent type = getReferenceTypeRef(container.getType());
+ if (type.hasTargetProfile() && !type.hasTargetProfile("http://hl7.org/fhir/StructureDefinition/Resource")) {
+ Set types = new HashSet<>();
+ List profiles = new ArrayList<>();
+ for (UriType u : type.getTargetProfile()) {
+ StructureDefinition sd = resolveProfile(profile, u.getValue());
+ if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, sd != null,messages.getString("Unable_to_resolve_the_profile_reference_"), u.getValue())) {
+ types.add(sd.getType());
+ if (ft.equals(sd.getType())) {
+ ok = true;
+ profiles.add(sd);
+ }
+ }
+ }
+ if (!pol.checkValid()) {
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() > 0,messages.getString("Unable_to_find_matching_profile_for__by_type_among_choices_"), ref, StringUtils.join("; ", type.getTargetProfile()));
+ } else {
+ Map> badProfiles = new HashMap>();
+ Map> goodProfiles = new HashMap>();
+ int goodCount = 0;
+ for (StructureDefinition pr : profiles) {
+ List profileErrors = new ArrayList();
+ validateResource(we.hostContext(hostContext, pr), profileErrors, we.getResource(), we.getFocus(), pr, IdStatus.OPTIONAL, we.getStack());
+ if (!hasErrors(profileErrors)) {
+ goodCount++;
+ goodProfiles.put(pr, profileErrors);
+ trackUsage(pr, hostContext, element);
+ } else {
+ badProfiles.put(pr, profileErrors);
+ }
+ }
+ if (goodCount == 1) {
+ if (showMessagesFromReferences) {
+ for (ValidationMessage vm : goodProfiles.values().iterator().next()) {
+ if (!errors.contains(vm)) {
+ errors.add(vm);
+ }
+ }
+ }
+
+ } else if (goodProfiles.size() == 0) {
+ if (!isShowMessagesFromReferences()) {
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles),messages.getString("Unable_to_find_matching_profile_for__among_choices_"), ref, asList(type.getTargetProfile()));
+ for (StructureDefinition sd : badProfiles.keySet()) {
+ slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for " + ref + " matching against Profile" + sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
+ }
+ } else {
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1,messages.getString("Unable_to_find_matching_profile_for__among_choices_"), ref, asList(type.getTargetProfile()));
+ for (List messages : badProfiles.values()) {
+ for (ValidationMessage vm : messages) {
+ if (!errors.contains(vm)) {
+ errors.add(vm);
+ }
+ }
+ }
+ }
+ } else {
+ if (!isShowMessagesFromReferences()) {
+ warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,messages.getString("Found_multiple_matching_profiles_for__among_choices_"), ref, asListByUrl(goodProfiles.keySet()));
+ for (StructureDefinition sd : badProfiles.keySet()) {
+ slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, "Details for " + ref + " matching against Profile" + sd.getUrl(), errorSummaryForSlicingAsHtml(badProfiles.get(sd)));
+ }
+ } else {
+ warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false,messages.getString("Found_multiple_matching_profiles_for__among_choices_"), ref, asListByUrl(goodProfiles.keySet()));
+ for (List messages : goodProfiles.values()) {
+ for (ValidationMessage vm : messages) {
+ if (!errors.contains(vm)) {
+ errors.add(vm);
+ }
+ }
+ }
+ }
+ }
+ }
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, ok,messages.getString("Invalid_Resource_target_type_Found__but_expected_one_of_"), ft, types.toString());
+ }
+ if (type.hasAggregation()) {
+ boolean modeOk = false;
+ for (Enumeration mode : type.getAggregation()) {
+ if (mode.getValue().equals(AggregationMode.CONTAINED) && refType.equals("contained"))
+ modeOk = true;
+ else if (mode.getValue().equals(AggregationMode.BUNDLED) && refType.equals("bundled"))
+ modeOk = true;
+ else if (mode.getValue().equals(AggregationMode.REFERENCED) && (refType.equals("bundled") || refType.equals("remote")))
+ modeOk = true;
+ }
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, modeOk,messages.getString("Reference_is__which_isnt_supported_by_the_specified_aggregation_modes_for_the_reference"), refType);
+ }
+ }
+ }
+ if (we == null) {
+ TypeRefComponent type = getReferenceTypeRef(container.getType());
+ boolean okToRef = !type.hasAggregation() || type.hasAggregation(AggregationMode.REFERENCED);
+ rule(errors, IssueType.REQUIRED, -1, -1, path, okToRef,messages.getString("Bundled_or_contained_reference_not_found_within_the_bundleresource_"), ref);
+ }
+ if (we == null && ft != null && assumeValidRestReferences) {
+ // if we == null, we inferred ft from the reference. if we are told to treat this as gospel
+ TypeRefComponent type = getReferenceTypeRef(container.getType());
+ Set types = new HashSet<>();
+ for (CanonicalType tp : type.getTargetProfile()) {
+ StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue());
+ if (sd != null) {
+ types.add(sd.getType());
+ }
+ }
+ rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || types.contains(ft),messages.getString("The_type__implied_by_the_reference_URL__is_not_a_valid_Target_for_this_element_must_be_one_of_"), ft, ref, types);
+
+ }
+ if (pol == ReferenceValidationPolicy.CHECK_VALID) {
+ // todo....
+ }
+ }
+
+ private String asListByUrl(Collection list) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (StructureDefinition sd : list) {
+ b.append(sd.getUrl());
+ }
+ return b.toString();
+ }
+
+ private String asList(Collection list) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (CanonicalType c : list) {
+ b.append(c.getValue());
+ }
+ return b.toString();
+ }
+
+ private boolean areAllBaseProfiles(List profiles) {
+ for (StructureDefinition sd : profiles) {
+ if (!sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private String errorSummaryForSlicing(List list) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (ValidationMessage vm : list) {
+ if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
+ b.append(vm.getLocation() + ": " + vm.getMessage());
+ }
+ }
+ return b.toString();
+ }
+
+ private String errorSummaryForSlicingAsHtml(List list) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (ValidationMessage vm : list) {
+ if (vm.isSlicingHint()) {
+ b.append("" + vm.getLocation() + ": " + vm.getSliceHtml() + "");
+ } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
+ b.append("" + vm.getLocation() + ": " + vm.getHtml() + "");
+ }
+ }
+ return "";
+ }
+
+ private TypeRefComponent getReferenceTypeRef(List types) {
+ for (TypeRefComponent tr : types) {
+ if ("Reference".equals(tr.getCode())) {
+ return tr;
+ }
+ }
+ return null;
+ }
+
+ private String checkResourceType(String type) {
+ long t = System.nanoTime();
+ try {
+ if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
+ return type;
+ else
+ return null;
+ } finally {
+ sdTime = sdTime + (System.nanoTime() - t);
+ }
+ }
+
+ private void checkSampledData(List errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern);
+ checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), fixedSource, "period", focus, pattern);
+ checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern);
+ checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern);
+ checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern);
+ checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern);
+ checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern);
+ }
+
+ private void checkTiming(List errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) {
+ checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern);
+
+ List events = new ArrayList();
+ focus.getNamedChildren("event", events);
+ if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(),messages.getString("Expected__but_found__event_elements"), Integer.toString(fixed.getEvent().size()), Integer.toString(events.size()))) {
+ for (int i = 0; i < events.size(); i++)
+ checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern);
+ }
+ }
+
+ private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
+ for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
+ if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
+ return true;
+ if (codeinExpansion(c, system, code))
return true;
}
+ return false;
+ }
- private String errorSummaryForSlicing(List list) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (ValidationMessage vm : list) {
- if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL || vm.isSlicingHint()) {
- b.append(vm.getLocation() + ": " + vm.getMessage());
- }
+ private boolean codeInExpansion(ValueSet vs, String system, String code) {
+ for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
+ if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
+ return true;
+ if (codeinExpansion(c, system, code))
+ return true;
+ }
+ return false;
+ }
+
+ private String describeReference(String reference) {
+ if (reference == null)
+ return "null";
+ return reference;
+ }
+
+ private String describeReference(String reference, CanonicalResource target) {
+ if (reference == null && target == null)
+ return "null";
+ if (reference == null) {
+ return target.getUrl();
+ }
+ if (target == null) {
+ return reference;
+ }
+ if (reference.equals(target.getUrl())) {
+ return reference;
+ }
+ return reference + "(which actually refers to " + target.getUrl() + ")";
+ }
+
+ private String describeTypes(List types) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (TypeRefComponent t : types) {
+ b.append(t.getWorkingCode());
+ }
+ return b.toString();
+ }
+
+ protected ElementDefinition findElement(StructureDefinition profile, String name) {
+ for (ElementDefinition c : profile.getSnapshot().getElement()) {
+ if (c.getPath().equals(name)) {
+ return c;
+ }
+ }
+ return null;
+ }
+
+ public BestPracticeWarningLevel getBestPracticeWarningLevel() {
+ return bpWarnings;
+ }
+
+ @Override
+ public CheckDisplayOption getCheckDisplay() {
+ return checkDisplay;
+ }
+
+ private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
+ if (code.equals(c.getCode()))
+ return c;
+ for (ConceptDefinitionComponent g : c.getConcept()) {
+ ConceptDefinitionComponent r = getCodeDefinition(g, code);
+ if (r != null)
+ return r;
+ }
+ return null;
+ }
+
+ private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
+ for (ConceptDefinitionComponent c : cs.getConcept()) {
+ ConceptDefinitionComponent r = getCodeDefinition(c, code);
+ if (r != null)
+ return r;
+ }
+ return null;
+ }
+
+ private IndexedElement getContainedById(Element container, String id) {
+ List contained = new ArrayList();
+ container.getNamedChildren("contained", contained);
+ for (int i = 0; i < contained.size(); i++) {
+ Element we = contained.get(i);
+ if (id.equals(we.getNamedChildValue("id"))) {
+ return new IndexedElement(i, we, null);
+ }
+ }
+ return null;
+ }
+
+ public IWorkerContext getContext() {
+ return context;
+ }
+
+ private List getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve) throws FHIRException {
+ List elements = new ArrayList();
+ if ("value".equals(discriminator) && element.hasFixed()) {
+ elements.add(element);
+ return elements;
+ }
+
+ if (removeResolve) { // if we're doing profile slicing, we don't want to walk into the last resolve.. we need the profile on the source not the target
+ if (discriminator.equals("resolve()")) {
+ elements.add(element);
+ return elements;
+ }
+ if (discriminator.endsWith(".resolve()"))
+ discriminator = discriminator.substring(0, discriminator.length() - 10);
+ }
+
+ ElementDefinition ed = null;
+ ExpressionNode expr = fpe.parse(fixExpr(discriminator));
+ long t2 = System.nanoTime();
+ ed = fpe.evaluateDefinition(expr, profile, element);
+ sdTime = sdTime + (System.nanoTime() - t2);
+ if (ed != null)
+ elements.add(ed);
+
+ for (TypeRefComponent type : element.getType()) {
+ for (CanonicalType p : type.getProfile()) {
+ String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null;
+ StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
+ if (sd == null)
+ throw new DefinitionException("Unable to resolve profile " + p);
+ profile = sd;
+ if (id == null)
+ element = sd.getSnapshot().getElementFirstRep();
+ else {
+ element = null;
+ for (ElementDefinition t : sd.getSnapshot().getElement()) {
+ if (id.equals(t.getId()))
+ element = t;
+ }
+ if (element == null)
+ throw new DefinitionException("Unable to resolve element " + id + " in profile " + p);
}
- return b.toString();
- }
-
- private String errorSummaryForSlicingAsHtml(List list) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (ValidationMessage vm : list) {
- if (vm.isSlicingHint()) {
- b.append("" + vm.getLocation() + ": " + vm.getSliceHtml() + "");
- } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) {
- b.append("" + vm.getLocation() + ": " + vm.getHtml() + "");
- }
- }
- return "";
- }
-
- private TypeRefComponent getReferenceTypeRef(List types) {
- for (TypeRefComponent tr : types) {
- if ("Reference".equals(tr.getCode())) {
- return tr;
- }
- }
- return null;
- }
-
- private String checkResourceType(String type) {
- long t = System.nanoTime();
- try {
- if (context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + type) != null)
- return type;
- else
- return null;
- } finally {
- sdTime = sdTime + (System.nanoTime() - t);
- }
- }
-
- private void checkSampledData(List errors, String path, Element focus, SampledData fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), fixedSource, "origin", focus, pattern);
- checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), fixedSource, "period", focus, pattern);
- checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), fixedSource, "factor", focus, pattern);
- checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), fixedSource, "lowerLimit", focus, pattern);
- checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), fixedSource, "upperLimit", focus, pattern);
- checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), fixedSource, "dimensions", focus, pattern);
- checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), fixedSource, "data", focus, pattern);
- }
-
- private void checkTiming(List errors, String path, Element focus, Timing fixed, String fixedSource, boolean pattern) {
- checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), fixedSource, "value", focus, pattern);
-
- List events = new ArrayList();
- focus.getNamedChildren("event", events);
- if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, events.size() == fixed.getEvent().size(),
- "Expected " + Integer.toString(fixed.getEvent().size()) + " but found " + Integer.toString(events.size()) + " event elements")) {
- for (int i = 0; i < events.size(); i++)
- checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), fixedSource, "event", focus, pattern);
- }
- }
-
- private boolean codeinExpansion(ValueSetExpansionContainsComponent cnt, String system, String code) {
- for (ValueSetExpansionContainsComponent c : cnt.getContains()) {
- if (code.equals(c.getCode()) && system.equals(c.getSystem().toString()))
- return true;
- if (codeinExpansion(c, system, code))
- return true;
- }
- return false;
- }
-
- private boolean codeInExpansion(ValueSet vs, String system, String code) {
- for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
- if (code.equals(c.getCode()) && (system == null || system.equals(c.getSystem())))
- return true;
- if (codeinExpansion(c, system, code))
- return true;
- }
- return false;
- }
-
- private String describeReference(String reference) {
- if (reference == null)
- return "null";
- return reference;
- }
-
- private String describeReference(String reference, CanonicalResource target) {
- if (reference == null && target == null)
- return "null";
- if (reference == null) {
- return target.getUrl();
- }
- if (target == null) {
- return reference;
- }
- if (reference.equals(target.getUrl())) {
- return reference;
- }
- return reference + "(which actually refers to " + target.getUrl() + ")";
- }
-
- private String describeTypes(List types) {
- CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
- for (TypeRefComponent t : types) {
- b.append(t.getWorkingCode());
- }
- return b.toString();
- }
-
- protected ElementDefinition findElement(StructureDefinition profile, String name) {
- for (ElementDefinition c : profile.getSnapshot().getElement()) {
- if (c.getPath().equals(name)) {
- return c;
- }
- }
- return null;
- }
-
- public BestPracticeWarningLevel getBestPracticeWarningLevel() {
- return bpWarnings;
- }
-
- @Override
- public CheckDisplayOption getCheckDisplay() {
- return checkDisplay;
- }
-
- private ConceptDefinitionComponent getCodeDefinition(ConceptDefinitionComponent c, String code) {
- if (code.equals(c.getCode()))
- return c;
- for (ConceptDefinitionComponent g : c.getConcept()) {
- ConceptDefinitionComponent r = getCodeDefinition(g, code);
- if (r != null)
- return r;
- }
- return null;
- }
-
- private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
- for (ConceptDefinitionComponent c : cs.getConcept()) {
- ConceptDefinitionComponent r = getCodeDefinition(c, code);
- if (r != null)
- return r;
- }
- return null;
- }
-
- private IndexedElement getContainedById(Element container, String id) {
- List contained = new ArrayList();
- container.getNamedChildren("contained", contained);
- for (int i = 0; i < contained.size(); i++) {
- Element we = contained.get(i);
- if (id.equals(we.getNamedChildValue("id"))) {
- return new IndexedElement(i, we, null);
- }
- }
- return null;
- }
-
- public IWorkerContext getContext() {
- return context;
- }
-
- private List getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve) throws FHIRException {
- List elements = new ArrayList();
- if ("value".equals(discriminator) && element.hasFixed()) {
- elements.add(element);
- return elements;
- }
-
- if (removeResolve) { // if we're doing profile slicing, we don't want to walk into the last resolve.. we need the profile on the source not the target
- if (discriminator.equals("resolve()")) {
- elements.add(element);
- return elements;
- }
- if (discriminator.endsWith(".resolve()"))
- discriminator = discriminator.substring(0, discriminator.length() - 10);
- }
-
- ElementDefinition ed = null;
- ExpressionNode expr = fpe.parse(fixExpr(discriminator));
- long t2 = System.nanoTime();
+ expr = fpe.parse(fixExpr(discriminator));
+ t2 = System.nanoTime();
ed = fpe.evaluateDefinition(expr, profile, element);
sdTime = sdTime + (System.nanoTime() - t2);
if (ed != null)
- elements.add(ed);
+ elements.add(ed);
+ }
+ }
+ return elements;
+ }
- for (TypeRefComponent type : element.getType()) {
- for (CanonicalType p : type.getProfile()) {
- String id = p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT) ? p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT) : null;
- StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
- if (sd == null)
- throw new DefinitionException("Unable to resolve profile " + p);
- profile = sd;
- if (id == null)
- element = sd.getSnapshot().getElementFirstRep();
- else {
- element = null;
- for (ElementDefinition t : sd.getSnapshot().getElement()) {
- if (id.equals(t.getId()))
- element = t;
- }
- if (element == null)
- throw new DefinitionException("Unable to resolve element " + id + " in profile " + p);
- }
- expr = fpe.parse(fixExpr(discriminator));
- t2 = System.nanoTime();
- ed = fpe.evaluateDefinition(expr, profile, element);
- sdTime = sdTime + (System.nanoTime() - t2);
- if (ed != null)
- elements.add(ed);
- }
+
+ private Element getExtensionByUrl(List extensions, String urlSimple) {
+ for (Element e : extensions) {
+ if (urlSimple.equals(e.getNamedChildValue("url")))
+ return e;
+ }
+ return null;
+ }
+
+ public List getExtensionDomains() {
+ return extensionDomains;
+ }
+
+ private IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List errors, String path, String type, boolean isTransaction) {
+ String targetUrl = null;
+ String version = "";
+ String resourceType = null;
+ if (ref.startsWith("http") || ref.startsWith("urn")) {
+ // We've got an absolute reference, no need to calculate
+ if (ref.contains("/_history/")) {
+ targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1);
+ version = ref.substring(ref.indexOf("/_history/") + 10);
+ } else
+ targetUrl = ref;
+
+ } else if (fullUrl == null) {
+ //This isn't a problem for signatures - if it's a signature, we won't have a resolution for a relative reference. For anything else, this is an error
+ // but this rule doesn't apply for batches or transactions
+ rule(errors, IssueType.REQUIRED, -1, -1, path, Utilities.existsInList(type, "batch-response", "transaction-response") || path.startsWith("Bundle.signature"),messages.getString("Relative_Reference_appears_inside_Bundle_whose_entry_is_missing_a_fullUrl"));
+ return null;
+
+ } else if (ref.split("/").length != 2 && ref.split("/").length != 4) {
+ if (isTransaction) {
+ rule(errors, IssueType.INVALID, -1, -1, path, isSearchUrl(ref),messages.getString("Relative_URLs_must_be_of_the_format_ResourceNameid_or_a_search_ULR_is_allowed_typeparameters__Encountered_"), ref);
+ } else {
+ rule(errors, IssueType.INVALID, -1, -1, path, false,messages.getString("Relative_URLs_must_be_of_the_format_ResourceNameid__Encountered_"), ref);
+ }
+ return null;
+
+ } else {
+ String base = "";
+ if (fullUrl.startsWith("urn")) {
+ String[] parts = fullUrl.split("\\:");
+ for (int i = 0; i < parts.length - 1; i++) {
+ base = base + parts[i] + ":";
}
- return elements;
- }
-
-
- private Element getExtensionByUrl(List extensions, String urlSimple) {
- for (Element e : extensions) {
- if (urlSimple.equals(e.getNamedChildValue("url")))
- return e;
+ } else {
+ String[] parts;
+ parts = fullUrl.split("/");
+ for (int i = 0; i < parts.length - 2; i++) {
+ base = base + parts[i] + "/";
}
- return null;
+ }
+
+ String id = null;
+ if (ref.contains("/_history/")) {
+ version = ref.substring(ref.indexOf("/_history/") + 10);
+ String[] refBaseParts = ref.substring(0, ref.indexOf("/_history/")).split("/");
+ resourceType = refBaseParts[0];
+ id = refBaseParts[1];
+ } else if (base.startsWith("urn")) {
+ resourceType = ref.split("/")[0];
+ id = ref.split("/")[1];
+ } else
+ id = ref;
+
+ targetUrl = base + id;
}
- public List getExtensionDomains() {
- return extensionDomains;
- }
-
- private IndexedElement getFromBundle(Element bundle, String ref, String fullUrl, List errors, String path, String type, boolean isTransaction) {
- String targetUrl = null;
- String version = "";
- String resourceType = null;
- if (ref.startsWith("http") || ref.startsWith("urn")) {
- // We've got an absolute reference, no need to calculate
- if (ref.contains("/_history/")) {
- targetUrl = ref.substring(0, ref.indexOf("/_history/") - 1);
- version = ref.substring(ref.indexOf("/_history/") + 10);
- } else
- targetUrl = ref;
-
- } else if (fullUrl == null) {
- //This isn't a problem for signatures - if it's a signature, we won't have a resolution for a relative reference. For anything else, this is an error
- // but this rule doesn't apply for batches or transactions
- rule(errors, IssueType.REQUIRED, -1, -1, path, Utilities.existsInList(type, "batch-response", "transaction-response") || path.startsWith("Bundle.signature"), "Relative Reference appears inside Bundle whose entry is missing a fullUrl");
- return null;
-
- } else if (ref.split("/").length != 2 && ref.split("/").length != 4) {
- if (isTransaction) {
- rule(errors, IssueType.INVALID, -1, -1, path, isSearchUrl(ref), "Relative URLs must be of the format [ResourceName]/[id], or a search ULR is allowed ([type]?parameters. Encountered " + ref + ")");
- } else {
- rule(errors, IssueType.INVALID, -1, -1, path, false, "Relative URLs must be of the format [ResourceName]/[id]. Encountered " + ref);
- }
- return null;
-
+ List entries = new ArrayList();
+ bundle.getNamedChildren("entry", entries);
+ Element match = null;
+ int matchIndex = -1;
+ for (int i = 0; i < entries.size(); i++) {
+ Element we = entries.get(i);
+ if (targetUrl.equals(we.getChildValue("fullUrl"))) {
+ Element r = we.getNamedChild("resource");
+ if (version.isEmpty()) {
+ rule(errors, IssueType.FORBIDDEN, -1, -1, path, match == null,messages.getString("Multiple_matches_in_bundle_for_reference_"), ref);
+ match = r;
+ matchIndex = i;
} else {
- String base = "";
- if (fullUrl.startsWith("urn")) {
- String[] parts = fullUrl.split("\\:");
- for (int i = 0; i < parts.length - 1; i++) {
- base = base + parts[i] + ":";
- }
- } else {
- String[] parts;
- parts = fullUrl.split("/");
- for (int i = 0; i < parts.length - 2; i++) {
- base = base + parts[i] + "/";
- }
+ try {
+ if (version.equals(r.getChildren("meta").get(0).getChildValue("versionId"))) {
+ rule(errors, IssueType.FORBIDDEN, -1, -1, path, match == null,messages.getString("Multiple_matches_in_bundle_for_reference_"), ref);
+ match = r;
+ matchIndex = i;
}
-
- String id = null;
- if (ref.contains("/_history/")) {
- version = ref.substring(ref.indexOf("/_history/") + 10);
- String[] refBaseParts = ref.substring(0, ref.indexOf("/_history/")).split("/");
- resourceType = refBaseParts[0];
- id = refBaseParts[1];
- } else if (base.startsWith("urn")) {
- resourceType = ref.split("/")[0];
- id = ref.split("/")[1];
- } else
- id = ref;
-
- targetUrl = base + id;
+ } catch (Exception e) {
+ warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren("meta").size() == 1 && r.getChildren("meta").get(0).getChildValue("versionId") != null,messages.getString("Entries_matching_fullURL__should_declare_metaversionId_because_there_are_versionspecific_references"), targetUrl);
+ // If one of these things is null
+ }
}
-
- List