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 44a586066..412210f65 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
@@ -9,9 +9,9 @@ package org.hl7.fhir.r5.validation.instancevalidator;
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -36,11 +36,13 @@ import java.util.Map;
import java.util.Set;
import java.util.UUID;
+import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
@@ -114,6 +116,7 @@ import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.TimeType;
import org.hl7.fhir.r5.model.Timing;
import org.hl7.fhir.r5.model.DataType;
+import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
@@ -148,5324 +151,5450 @@ import ca.uhn.fhir.util.ObjectUtil;
/**
- * Thinking of using this in a java program? Don't!
+ * Thinking of using this in a java program? Don't!
* You should use one of the wrappers instead. Either in HAPI, or use ValidationEngine
- *
+ *
* Validation todo:
* - support @default slices
- *
- * @author Grahame Grieve
*
+ * @author Grahame Grieve
*/
-/*
+/*
* todo:
* check urn's don't start oid: or uuid:
- * check MetadataResource.url is absolute
+ * check MetadataResource.url is absolute
*/
public class InstanceValidator extends BaseValidator implements IResourceValidator {
- /**
- * The validator keeps one of these classes for each resource it validates (e.g. when chasing references)
- *
- * It keeps track of the state of resource validation - a given resrouce may be validated agaisnt multiple
- * profiles during a single validation, and it may be valid against some, and invalid against others.
- *
- * We don't want to keep doing the same validation, so we remember the outcomes for each combination
- * of resource + profile
- *
- * Additionally, profile validation may be circular - e.g. a Patient profile that applies the same profile
- * to linked patients, and 2 patient resources that link to each other. So this class also tracks validation
- * in process.
- *
- * If the validator comes back to the same point, and validates the same instance against the same profile
- * while it's already validating, it assumes it's valid - it cannot have new errors that wouldn't already
- * be found in the first iteration - in other words, there's no errors
- *
- * @author graha
- *
- */
- private class ResourceValidationTracker {
- private Map> validations = new HashMap<>();
-
- private void startValidating(StructureDefinition sd) {
- validations.put(sd.getUrl(), new ArrayList());
- }
-
- private List getOutcomes(StructureDefinition sd) {
- return validations.get(sd.getUrl());
- }
+ private class ValidatorHostServices implements IEvaluationContext {
- public void storeOutcomes(StructureDefinition sd, List errors) {
- validations.put(sd.getUrl(), errors);
- }
- }
-
- 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(this);
- 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;
- }
- }
- }
-
- @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();
+ @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;
}
- checkElementUsage(errors, ce, stack.push(ce, elementCount, null, 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 boolean check(String v1, String v2) {
- return v1 == null ? Utilities.noString(v1) : v1.equals(v2);
- }
+ private IWorkerContext context;
+ private FHIRPathEngine fpe;
- 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);
+ // 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;
- 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 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 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);
- }
+ @Override
+ public boolean isNoExtensibleWarnings() {
+ return noExtensibleWarnings;
+ }
- // 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() + "'");
+ @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;
- }
- } 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()+")");
+ 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 {
- 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;
- }
- }
- 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());
- }
- }
- return res;
- }
-
- 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);
-
- }
-
- 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());
- }
- }
- 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 (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;
- }
- }
-
- 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);
-
- 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"));
- }
- }
- }
- }
- }
-
- 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 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");
- }
-
- 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(), "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 + "'");
-
- 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
- }
-
- 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"
-
- ), "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, "Illegal attribute name in the XHTML ('"+an+"' on '"+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), "Wrong namespace on the XHTML ('"+ns+"', should be '"+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, "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");
- }
-
- 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")), "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 {
+ 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 {
- 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), "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,
- "The type '"+reference.getType()+"' is not a valid Target for this element (must be one of "+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()), "The specified type '"+reference.getType()+"' does not match the found type '"+ft+"'");
- }
-
- 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);
+ 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 {
- 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 + "[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");
}
- }
- 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");
+ // 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;
}
- 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());
+
+ 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;
+ }
}
- }
- 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+")");
-
+ return false;
}
- 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
+ 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;
+ }
+ }
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 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;
}
- }
- 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))
+ 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);
+
+ }
+
+ 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());
+ }
+ }
+ 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 (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;
+ }
+ }
+
+ 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);
+
+ 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"));
+ }
+ }
+ }
+ }
+ }
+
+ 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 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");
+ }
+
+ 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(), "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 + "'");
+
+ 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
+ }
+
+ 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"
+
+ ), "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, "Illegal attribute name in the XHTML ('" + an + "' on '" + 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), "Wrong namespace on the XHTML ('" + ns + "', should be '" + 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, "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");
+ }
+
+ 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")), "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), "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,
+ "The type '" + reference.getType() + "' is not a valid Target for this element (must be one of " + 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()), "The specified type '" + reference.getType() + "' does not match the found type '" + ft + "'");
+ }
+
+ 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());
+ }
+ 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;
}
- 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;
+ 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();
}
- 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();
+ 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 "";
}
- 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());
+ private TypeRefComponent getReferenceTypeRef(List types) {
+ for (TypeRefComponent tr : types) {
+ if ("Reference".equals(tr.getCode())) {
+ return tr;
+ }
+ }
+ return null;
}
- return b.toString();
- }
- protected ElementDefinition findElement(StructureDefinition profile, String name) {
- for (ElementDefinition c : profile.getSnapshot().getElement()) {
- if (c.getPath().equals(name)) {
- return c;
- }
+ 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);
+ }
}
- 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;
+ 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);
}
- return null;
- }
- private ConceptDefinitionComponent getCodeDefinition(CodeSystem cs, String code) {
- for (ConceptDefinitionComponent c : cs.getConcept()) {
- ConceptDefinitionComponent r = getCodeDefinition(c, code);
- if (r != null)
- return r;
+ 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;
}
- 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);
+ 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);
+ }
}
- expr = fpe.parse(fixExpr(discriminator));
- t2 = System.nanoTime();
+ 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);
- }
- }
- return elements;
- }
+ 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"), "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;
-
- } 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] + "/";
- }
- }
-
- 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;
- }
-
- 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, "Multiple matches in bundle for reference " + ref);
- match = r;
- matchIndex = i;
- } else {
- try {
- if (version.equals(r.getChildren("meta").get(0).getChildValue("versionId"))) {
- rule(errors, IssueType.FORBIDDEN, -1, -1, path, match==null, "Multiple matches in bundle for reference " + ref);
- match = r;
- matchIndex = i;
+ 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);
}
- } catch (Exception e) {
- warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren("meta").size()==1 && r.getChildren("meta").get(0).getChildValue("versionId")!=null, "Entries matching fullURL " + targetUrl + " should declare meta/versionId because there are version-specific references");
- // If one of these things is null
- }
}
- }
+ return elements;
}
- if (match!=null && resourceType!=null)
- rule(errors, IssueType.REQUIRED, -1, -1, path, match.getType().equals(resourceType), "Matching reference for reference " + ref + " has resourceType " + match.getType());
- if (match == null)
- warning(errors, IssueType.REQUIRED, -1, -1, path, !ref.startsWith("urn"), "URN reference is not locally contained within the bundle " + ref);
- return match == null ? null : new IndexedElement(matchIndex, match, entries.get(matchIndex));
- }
- private boolean isSearchUrl(String ref) {
- if (Utilities.noString(ref) || !ref.contains("?")) {
- return false;
- }
- String tn = ref.substring(0, ref.indexOf("?"));
- String q = ref.substring(ref.indexOf("?")+1);
- if (!context.getResourceNames().contains(tn)) {
- return false;
- } else {
- return q.matches("([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+)(&([_a-zA-Z][_a-zA-Z0-9]*=[^=&]+))*");
- }
- }
-
- private StructureDefinition getProfileForType(String type, List list) {
- for (TypeRefComponent tr : list) {
- String url = tr.getWorkingCode();
- if (!Utilities.isAbsoluteUrl(url))
- url = "http://hl7.org/fhir/StructureDefinition/" + url;
- long t = System.nanoTime();
- StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
- sdTime = sdTime + (System.nanoTime() - t);
- if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot())
- return sd;
- }
- return null;
- }
-
- private Element getValueForDiscriminator(Object appContext, List errors, Element element, String discriminator, ElementDefinition criteria, NodeStack stack) throws FHIRException, IOException {
- String p = stack.getLiteralPath()+"."+element.getName();
- Element focus = element;
- String[] dlist = discriminator.split("\\.");
- for (String d : dlist) {
- if (focus.fhirType().equals("Reference") && d.equals("reference")) {
- String url = focus.getChildValue("reference");
- if (Utilities.noString(url))
- throw new FHIRException("No reference resolving discriminator "+discriminator+" from "+element.getProperty().getName());
- // Note that we use the passed in stack here. This might be a problem if the discriminator is deep enough?
- Element target = resolve(appContext, url, stack, errors, p);
- if (target == null)
- throw new FHIRException("Unable to find resource "+url+" at "+d+" resolving discriminator "+discriminator+" from "+element.getProperty().getName());
- focus = target;
- } else if (d.equals("value") && focus.isPrimitive()) {
- return focus;
- } else {
- List