diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java index 0697a45a4..5f3df1548 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/r5/validation/InstanceValidator.java @@ -130,6 +130,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ValidationProfileSet; import org.hl7.fhir.r5.utils.ValidationProfileSet.ProfileRegistration; import org.hl7.fhir.r5.validation.EnableWhenEvaluator.QStack; +import org.hl7.fhir.r5.validation.InstanceValidator.EntrySummary; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.TerminologyServiceOptions; import org.hl7.fhir.utilities.Utilities; @@ -365,6 +366,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private long fpeTime = 0; private boolean noBindingMsgSuppressed; + private boolean debug; private HashMap resourceProfilesMap; private IValidatorResourceFetcher fetcher; long time = 0; @@ -878,31 +880,31 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return v1 == null ? Utilities.noString(v1) : v1.equals(v2); } - private void checkAddress(List errors, String path, Element focus, Address fixed) { - checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); - checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); - checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city", focus); - checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state", focus); - checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country", focus); - checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "postalCode", focus); + private void checkAddress(List errors, String path, Element focus, Address fixed, boolean pattern) { + checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus, pattern); + checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus, pattern); + checkFixedValue(errors, path + ".city", focus.getNamedChild("city"), fixed.getCityElement(), "city", focus, pattern); + checkFixedValue(errors, path + ".state", focus.getNamedChild("state"), fixed.getStateElement(), "state", focus, pattern); + checkFixedValue(errors, path + ".country", focus.getNamedChild("country"), fixed.getCountryElement(), "country", focus, pattern); + checkFixedValue(errors, path + ".zip", focus.getNamedChild("zip"), fixed.getPostalCodeElement(), "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), "coding", focus); + checkFixedValue(errors, path + ".coding", lines.get(i), fixed.getLine().get(i), "coding", focus, pattern); } } - private void checkAttachment(List errors, String path, Element focus, Attachment fixed) { - checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType", focus); - checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language", focus); - checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus); - checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url", focus); - checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size", focus); - checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash", focus); - checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title", focus); + private void checkAttachment(List errors, String path, Element focus, Attachment fixed, boolean pattern) { + checkFixedValue(errors, path + ".contentType", focus.getNamedChild("contentType"), fixed.getContentTypeElement(), "contentType", focus, pattern); + checkFixedValue(errors, path + ".language", focus.getNamedChild("language"), fixed.getLanguageElement(), "language", focus, pattern); + checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus, pattern); + checkFixedValue(errors, path + ".url", focus.getNamedChild("url"), fixed.getUrlElement(), "url", focus, pattern); + checkFixedValue(errors, path + ".size", focus.getNamedChild("size"), fixed.getSizeElement(), "size", focus, pattern); + checkFixedValue(errors, path + ".hash", focus.getNamedChild("hash"), fixed.getHashElement(), "hash", focus, pattern); + checkFixedValue(errors, path + ".title", focus.getNamedChild("title"), fixed.getTitleElement(), "title", focus, pattern); } // public API @@ -983,7 +985,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private void checkCodeableConcept(List errors, String path, Element focus, CodeableConcept fixed, boolean pattern) { - checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); + checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus, pattern); List codings = new ArrayList(); focus.getNamedChildren("coding", codings); if (pattern) { @@ -997,7 +999,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat List errorsFixed; for (int j = 0; j < codings.size() && !found; ++j) { errorsFixed = new ArrayList<>(); - checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, "coding", focus); + checkFixedValue(errorsFixed, path + ".coding", codings.get(j), fixedCoding, "coding", focus, pattern); if (!hasErrors(errorsFixed)) { found = true; } else { @@ -1010,7 +1012,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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 fixed CodeableConcept not found for" + + String message = "Expected CodeableConcept "+(pattern ? "pattern" : "fixed value")+" not found for" + " system: " + fixedCoding.getSystemElement().asStringValue() + " code: " + fixedCoding.getCodeElement().asStringValue() + " display: " + fixedCoding.getDisplayElement().asStringValue(); @@ -1195,11 +1197,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return b.toString(); } - private void checkCoding(List errors, String path, Element focus, Coding fixed) { - checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); - checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus); - checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display", focus); - checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected", focus); + private void checkCoding(List errors, String path, Element focus, Coding fixed, boolean pattern) { + checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus, pattern); + checkFixedValue(errors, path + ".version", focus.getNamedChild("version"), fixed.getVersionElement(), "version", focus, pattern); + checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus, pattern); + checkFixedValue(errors, path + ".display", focus.getNamedChild("display"), fixed.getDisplayElement(), "display", focus, pattern); + checkFixedValue(errors, path + ".userSelected", focus.getNamedChild("userSelected"), fixed.getUserSelectedElement(), "userSelected", focus, pattern); } private void checkCoding(List errors, String path, Element element, StructureDefinition profile, ElementDefinition theElementCntext, boolean inCodeableConcept, boolean checkDisplay, NodeStack stack) { @@ -1275,11 +1278,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } - private void checkContactPoint(List errors, String path, Element focus, ContactPoint fixed) { - checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); - checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); - checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); - checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); + private void checkContactPoint(List errors, String path, Element focus, ContactPoint fixed, boolean pattern) { + checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus, pattern); + checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus, pattern); + checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus, pattern); + checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus, pattern); } @@ -1456,8 +1459,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkFixedValue(List errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String propName, Element parent, boolean pattern) { if ((fixed == null || fixed.isEmpty()) && focus == null) ; // this is all good - else if (fixed == null && focus != null) - rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, false, "Unexpected element " + focus.getName()); + else if ((fixed == null || fixed.isEmpty()) && focus != null) + rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, pattern, "Unexpected element " + focus.getName()); 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+"'"); else { @@ -1505,31 +1508,31 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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); + checkQuantity(errors, path, focus, (Quantity) fixed, pattern); else if (fixed instanceof Address) - checkAddress(errors, path, focus, (Address) fixed); + checkAddress(errors, path, focus, (Address) fixed, pattern); else if (fixed instanceof ContactPoint) - checkContactPoint(errors, path, focus, (ContactPoint) fixed); + checkContactPoint(errors, path, focus, (ContactPoint) fixed, pattern); else if (fixed instanceof Attachment) - checkAttachment(errors, path, focus, (Attachment) fixed); + checkAttachment(errors, path, focus, (Attachment) fixed, pattern); else if (fixed instanceof Identifier) - checkIdentifier(errors, path, focus, (Identifier) fixed); + checkIdentifier(errors, path, focus, (Identifier) fixed, pattern); else if (fixed instanceof Coding) - checkCoding(errors, path, focus, (Coding) fixed); + checkCoding(errors, path, focus, (Coding) fixed, pattern); else if (fixed instanceof HumanName) - checkHumanName(errors, path, focus, (HumanName) fixed); + checkHumanName(errors, path, focus, (HumanName) fixed, pattern); else if (fixed instanceof CodeableConcept) checkCodeableConcept(errors, path, focus, (CodeableConcept) fixed, pattern); else if (fixed instanceof Timing) - checkTiming(errors, path, focus, (Timing) fixed); + checkTiming(errors, path, focus, (Timing) fixed, pattern); else if (fixed instanceof Period) - checkPeriod(errors, path, focus, (Period) fixed); + checkPeriod(errors, path, focus, (Period) fixed, pattern); else if (fixed instanceof Range) - checkRange(errors, path, focus, (Range) fixed); + checkRange(errors, path, focus, (Range) fixed, pattern); else if (fixed instanceof Ratio) - checkRatio(errors, path, focus, (Ratio) fixed); + checkRatio(errors, path, focus, (Ratio) fixed, pattern); else if (fixed instanceof SampledData) - checkSampledData(errors, path, focus, (SampledData) fixed); + checkSampledData(errors, path, focus, (SampledData) fixed, pattern); else rule(errors, IssueType.EXCEPTION, focus.line(), focus.col(), path, false, "Unhandled fixed value type " + fixed.getClass().getName()); @@ -1549,35 +1552,35 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } - private void checkHumanName(List errors, String path, Element focus, HumanName fixed) { - checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); - checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus); - checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); + private void checkHumanName(List errors, String path, Element focus, HumanName fixed, boolean pattern) { + checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus, pattern); + checkFixedValue(errors, path + ".text", focus.getNamedChild("text"), fixed.getTextElement(), "text", focus, pattern); + checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "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(), "family", focus); + checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), "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), "given", focus); + checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), "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), "prefix", focus); + checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), "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), "suffix", focus); + checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), "suffix", focus, pattern); } } @@ -1586,18 +1589,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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) { - checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus); - checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type", focus); - checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); - checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); - checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus); - checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner", focus); + private void checkIdentifier(List errors, String path, Element focus, Identifier fixed, boolean pattern) { + checkFixedValue(errors, path + ".use", focus.getNamedChild("use"), fixed.getUseElement(), "use", focus, pattern); + checkFixedValue(errors, path + ".type", focus.getNamedChild("type"), fixed.getType(), "type", focus, pattern); + checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus, pattern); + checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus, pattern); + checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), "period", focus, pattern); + checkFixedValue(errors, path + ".assigner", focus.getNamedChild("assigner"), fixed.getAssigner(), "assigner", focus, pattern); } - private void checkPeriod(List errors, String path, Element focus, Period fixed) { - checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start", focus); - checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus); + private void checkPeriod(List errors, String path, Element focus, Period fixed, boolean pattern) { + checkFixedValue(errors, path + ".start", focus.getNamedChild("start"), fixed.getStartElement(), "start", focus, pattern); + checkFixedValue(errors, path + ".end", focus.getNamedChild("end"), fixed.getEndElement(), "end", focus, pattern); } private void checkPrimitive(Object appContext, List errors, String path, String type, ElementDefinition context, Element e, StructureDefinition profile, NodeStack node) throws FHIRException, IOException { @@ -1866,25 +1869,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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) { - checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus); - checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator", focus); - checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units", focus); - checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus); - checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus); + private void checkQuantity(List errors, String path, Element focus, Quantity fixed, boolean pattern) { + checkFixedValue(errors, path + ".value", focus.getNamedChild("value"), fixed.getValueElement(), "value", focus, pattern); + checkFixedValue(errors, path + ".comparator", focus.getNamedChild("comparator"), fixed.getComparatorElement(), "comparator", focus, pattern); + checkFixedValue(errors, path + ".units", focus.getNamedChild("unit"), fixed.getUnitElement(), "units", focus, pattern); + checkFixedValue(errors, path + ".system", focus.getNamedChild("system"), fixed.getSystemElement(), "system", focus, pattern); + checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), "code", focus, pattern); } // implementation - private void checkRange(List errors, String path, Element focus, Range fixed) { - checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low", focus); - checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high", focus); + private void checkRange(List errors, String path, Element focus, Range fixed, boolean pattern) { + checkFixedValue(errors, path + ".low", focus.getNamedChild("low"), fixed.getLow(), "low", focus, pattern); + checkFixedValue(errors, path + ".high", focus.getNamedChild("high"), fixed.getHigh(), "high", focus, pattern); } - private void checkRatio(List errors, String path, Element focus, Ratio fixed) { - checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator", focus); - checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator", focus); + private void checkRatio(List errors, String path, Element focus, Ratio fixed, boolean pattern) { + checkFixedValue(errors, path + ".numerator", focus.getNamedChild("numerator"), fixed.getNumerator(), "numerator", focus, pattern); + checkFixedValue(errors, path + ".denominator", focus.getNamedChild("denominator"), fixed.getDenominator(), "denominator", focus, pattern); } private void checkReference(ValidatorHostContext hostContext, List errors, String path, Element element, StructureDefinition profile, ElementDefinition container, String parentType, NodeStack stack) throws FHIRException, IOException { @@ -2081,25 +2084,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } - private void checkSampledData(List errors, String path, Element focus, SampledData fixed) { - checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin", focus); - checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period", focus); - checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor", focus); - checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit", focus); - checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit", focus); - checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions", focus); - checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus); + private void checkSampledData(List errors, String path, Element focus, SampledData fixed, boolean pattern) { + checkFixedValue(errors, path + ".origin", focus.getNamedChild("origin"), fixed.getOrigin(), "origin", focus, pattern); + checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriodElement(), "period", focus, pattern); + checkFixedValue(errors, path + ".factor", focus.getNamedChild("factor"), fixed.getFactorElement(), "factor", focus, pattern); + checkFixedValue(errors, path + ".lowerLimit", focus.getNamedChild("lowerLimit"), fixed.getLowerLimitElement(), "lowerLimit", focus, pattern); + checkFixedValue(errors, path + ".upperLimit", focus.getNamedChild("upperLimit"), fixed.getUpperLimitElement(), "upperLimit", focus, pattern); + checkFixedValue(errors, path + ".dimensions", focus.getNamedChild("dimensions"), fixed.getDimensionsElement(), "dimensions", focus, pattern); + checkFixedValue(errors, path + ".data", focus.getNamedChild("data"), fixed.getDataElement(), "data", focus, pattern); } - private void checkTiming(List errors, String path, Element focus, Timing fixed) { - checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "value", focus); + private void checkTiming(List errors, String path, Element focus, Timing fixed, boolean pattern) { + checkFixedValue(errors, path + ".repeat", focus.getNamedChild("repeat"), fixed.getRepeat(), "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), "event", focus); + checkFixedValue(errors, path + ".event", events.get(i), fixed.getEvent().get(i), "event", focus, pattern); } } @@ -3731,7 +3734,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) { validateDocument(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id); } - checkAllInterlinked(errors, entries, stack, bundle); + checkAllInterlinked(errors, entries, stack, bundle, false); } if (type.equals("message")) { Element resource = firstEntry.getNamedChild("resource"); @@ -3739,7 +3742,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, "No resource on first entry")) { validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id); } - checkAllInterlinked(errors, entries, stack, bundle); + checkAllInterlinked(errors, entries, stack, bundle, true); } // We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles // validateResourceIds(errors, entries, stack); @@ -3815,58 +3818,89 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L } } - private void checkAllInterlinked(List errors, List entries, NodeStack stack, Element bundle) { - Map visitedResources = new HashMap(); - HashMap candidateEntries = new HashMap(); - List candidateResources = new ArrayList(); + public class EntrySummary { + Element entry; + Element resource; + List targets = new ArrayList<>(); + public EntrySummary(Element entry, Element resource) { + this.entry = entry; + this.resource = resource; + } + + } + + private void checkAllInterlinked(List errors, List entries, NodeStack stack, Element bundle, boolean isError) { + List entryList = new ArrayList<>(); for (Element entry: entries) { - candidateEntries.put(entry.getNamedChild("resource"), entry); - candidateResources.add(entry.getNamedChild("resource")); - } - // Find resources that are pointed to as stylesheet links - List sheets = new ArrayList<>(); - List links = bundle.getChildren("link"); - for (Element link : links) { - if (link.getChildValue("relation").equals("stylesheet")) { - sheets.add(link.getChildValue("url")); + Element r = entry.getNamedChild("resource"); + if (r != null) { + entryList.add(new EntrySummary(entry, r)); } } - - if (!sheets.isEmpty()) { - for (Element r : candidateResources) { - String url = r.getChildValue("fullUrl"); - if (sheets.contains(url)) - visitedResources.put(url, r); - } - } - - List unusedResources = new ArrayList(); - boolean reverseLinksFound; - do { - reverseLinksFound = false; - followResourceLinks(entries.get(0), visitedResources, candidateEntries, candidateResources, errors, stack); - unusedResources.clear(); - unusedResources.addAll(candidateResources); - unusedResources.removeAll(visitedResources.values()); - for (Element unusedResource: unusedResources) { - List references = findReferences(unusedResource); - for (String reference: references) { - if (!visitedResources.containsKey(reference)) { - visitedResources.put(reference, unusedResource); - reverseLinksFound = true; + for (EntrySummary e : entryList) { + Set references = findReferences(e.entry); + for (String ref : references) { + Element tgt = resolveInBundle(entries, ref, e.entry.getChildValue("fullUrl"), e.resource.fhirType(), e.resource.getIdBase()); + if (tgt != null) { + EntrySummary t = entryForTarget(entryList, tgt); + if (t != null) { + e.targets.add(t); } } } - } while (reverseLinksFound); - // Lloyd Todo - Loop above four lines to check if the remaining unused resources point *to* any of the visitedResources and if so, add those to the visitedList until nothing more is added. Any that are still left over are errors + } + Set visited = new HashSet<>(); + visitLinked(visited, entryList.get(0)); + boolean foundRevLinks; + do { + foundRevLinks = false; + for (EntrySummary e : entryList) { + if (!visited.contains(e)) { + boolean add = false; + for (EntrySummary t : e.targets) { + if (visited.contains(t)) { + add = true; + } + } + if (add) { + foundRevLinks = true; + visitLinked(visited, e); + } + } + } + } while (foundRevLinks); + int i = 0; - for (Element entry : entries) { - rule(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry" + '[' + (i+1) + ']'), !unusedResources.contains(entry.getNamedChild("resource")), "Entry isn't reachable by traversing from first Bundle entry"); + for (EntrySummary e : entryList) { + Element entry = e.entry; + if (isError) { + rule(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry" + '[' + (i+1) + ']'), visited.contains(e), "Entry "+(entry.getChildValue("fullUrl") != null ? "'"+entry.getChildValue("fullUrl")+"'" : "")+" isn't reachable by traversing from first Bundle entry"); + } else { + warning(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry" + '[' + (i+1) + ']'), visited.contains(e), "Entry "+(entry.getChildValue("fullUrl") != null ? "'"+entry.getChildValue("fullUrl")+"'" : "")+" isn't reachable by traversing from first Bundle entry"); + } i++; } } + private EntrySummary entryForTarget(List entryList, Element tgt) { + for (EntrySummary e : entryList) { + if (e.entry == tgt) { + return e; + } + } + return null; + } + + private void visitLinked(Set visited, EntrySummary t) { + if (!visited.contains(t)) { + visited.add(t); + for (EntrySummary e : t.targets) { + visitLinked(visited, e); + } + } + } + private void followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack) { followResourceLinks(entry, visitedResources, candidateEntries, candidateResources, errors, stack, 0); } @@ -3879,7 +3913,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L visitedResources.put(entry.getNamedChildValue("fullUrl"), resource); String type = null; - List references = findReferences(resource); + Set references = findReferences(resource); for (String reference: references) { // We don't want errors when just retrieving the element as they will be caught (with better path info) in subsequent processing Element r = getFromBundle(stack.getElement(), reference, entry.getChildValue("fullUrl"), new ArrayList(), stack.addToLiteralPath("entry[" + candidateResources.indexOf(resource) + "]"), type); @@ -3889,17 +3923,22 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L } } - private List findReferences(Element start) { - List references = new ArrayList(); + private Set findReferences(Element start) { + Set references = new HashSet(); findReferences(start, references); return references; } - private void findReferences(Element start, List references) { + private void findReferences(Element start, Set references) { for (Element child : start.getChildren()) { if (child.getType().equals("Reference")) { String ref = child.getChildValue("reference"); - if (ref!=null && !ref.startsWith("#")) + if (ref != null && !ref.startsWith("#")) + references.add(ref); + } + if (child.getType().equals("url") || child.getType().equals("uri") || child.getType().equals("canonical")) { + String ref = child.primitiveValue(); + if (ref != null && !ref.startsWith("#")) references.add(ref); } findReferences(child, references); @@ -3993,9 +4032,14 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext) throws FHIRException, FHIRException, IOException { // element.markValidation(profile, definition); - // System.out.println(" "+stack.getLiteralPath()+" "+Long.toString((System.nanoTime() - time) / 1000000)); + if (debug) { + System.out.println(" "+stack.getLiteralPath()); + } // time = System.nanoTime(); - checkInvariants(hostContext, errors, profile, definition, resource, element, stack); + // check type invariants + checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false); + if (definition.getFixed() != null) + checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), definition.getSliceName(), null); // get the list of direct defined children, including slices @@ -4033,26 +4077,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L if (ei.definition != null) { String type = null; ElementDefinition typeDefn = null; - - String usesMustSupport = profile.getUserString("usesMustSupport"); - if (usesMustSupport == null) { - usesMustSupport = "N"; - for (ElementDefinition pe: profile.getSnapshot().getElement()) { - if (pe.getMustSupport()) { - usesMustSupport = "Y"; - break; - } - } - profile.setUserData("usesMustSupport", usesMustSupport); - } - if (usesMustSupport.equals("Y")) { - String elementSupported = ei.element.getUserString("elementSupported"); - if (elementSupported==null || ei.definition.getMustSupport()) - if (ei.definition.getMustSupport()) - ei.element.setUserData("elementSupported", "Y"); - else - ei.element.setUserData("elementSupported", "N"); - } + checkMustSupport(profile, ei); if (ei.definition.getType().size() == 1 && !"*".equals(ei.definition.getType().get(0).getWorkingCode()) && !"Element".equals(ei.definition.getType().get(0).getWorkingCode()) && !"BackboneElement".equals(ei.definition.getType().get(0).getWorkingCode())) { @@ -4120,6 +4145,8 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L boolean thisIsCodeableConcept = false; boolean checkDisplay = true; + checkInvariants(hostContext, errors, profile, ei.definition, resource, ei.element, localStack, true); + ei.element.markValidation(profile, ei.definition); if (type != null) { if (isPrimitiveType(type)) { @@ -4228,6 +4255,28 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L } } + public void checkMustSupport(StructureDefinition profile, ElementInfo ei) { + String usesMustSupport = profile.getUserString("usesMustSupport"); + if (usesMustSupport == null) { + usesMustSupport = "N"; + for (ElementDefinition pe: profile.getSnapshot().getElement()) { + if (pe.getMustSupport()) { + usesMustSupport = "Y"; + break; + } + } + profile.setUserData("usesMustSupport", usesMustSupport); + } + if (usesMustSupport.equals("Y")) { + String elementSupported = ei.element.getUserString("elementSupported"); + if (elementSupported==null || ei.definition.getMustSupport()) + if (ei.definition.getMustSupport()) + ei.element.setUserData("elementSupported", "Y"); + else + ei.element.setUserData("elementSupported", "N"); + } + } + public void checkCardinalities(List errors, StructureDefinition profile, Element element, NodeStack stack, List childDefinitions, List children, List problematicPaths) throws DefinitionException { // 3. report any definitions that have a cardinality problem @@ -4363,7 +4412,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L } public void checkInvariants(ValidatorHostContext hostContext, List errors, StructureDefinition profile, ElementDefinition definition, - Element resource, Element element, NodeStack stack) throws FHIRException { + Element resource, Element element, NodeStack stack, boolean onlyNonInherited) throws FHIRException { // this was an old work around for resource/rootresource issue. // if (resource.getName().equals("contained")) { // NodeStack ancestor = stack; @@ -4372,9 +4421,7 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L // if (ancestor != null && ancestor.element != null) // checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, ancestor.element, element); // } else - checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element); - if (definition.getFixed()!=null) - checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), definition.getSliceName(), null); + checkInvariants(hostContext, errors, stack.getLiteralPath(), profile, definition, null, null, resource, element, onlyNonInherited); } public boolean matchSlice(ValidatorHostContext hostContext, List errors, StructureDefinition profile, NodeStack stack, @@ -4468,12 +4515,12 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L return IdStatus.REQUIRED; } - private void checkInvariants(ValidatorHostContext hostContext, List errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element) throws FHIRException, FHIRException { + private void checkInvariants(ValidatorHostContext hostContext, List errors, String path, StructureDefinition profile, ElementDefinition ed, String typename, String typeProfile, Element resource, Element element, boolean onlyNonInherited) throws FHIRException, FHIRException { if (noInvariantChecks) return; for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) { - if (inv.hasExpression()) { + if (inv.hasExpression() && (!onlyNonInherited || !inv.hasSource() || profile.getUrl().equals(inv.getSource()))) { @SuppressWarnings("unchecked") Set invList = executionId.equals(element.getUserString(EXECUTION_ID)) ? (Set) element.getUserData(EXECUTED_CONSTRAINT_LIST) : null; if (invList == null) { @@ -4951,6 +4998,9 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L private String fixExpr(String expr) { // this is a hack work around for past publication of wrong FHIRPath expressions // R4 + // waiting for 4.0.2 + + // handled in 4.0.1 if ("(component.empty() and hasMember.empty()) implies (dataAbsentReason or value)".equals(expr)) return "(component.empty() and hasMember.empty()) implies (dataAbsentReason.exists() or value.exists())"; if ("isModifier implies isModifierReason.exists()".equals(expr)) @@ -4999,5 +5049,13 @@ private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, L this.validationLanguage = validationLanguage; } + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java index 7e5f80253..4e7b7c374 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTestSuite.java @@ -113,6 +113,7 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour String testCaseContent = TestingUtilities.loadTestResource("validator", name.substring(name.indexOf(".")+1)); InstanceValidator val = ve.getValidator(); + val.setDebug(false); if (content.has("allowed-extension-domain")) val.getExtensionDomains().add(content.get("allowed-extension-domain").getAsString()); if (content.has("allowed-extension-domains"))