diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java index cfb299dc3..726629a90 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java @@ -544,6 +544,7 @@ public class ValidationMessage implements Comparator, Compara private String invId; private String comment; private List sliceInfo; + private int count; /** * Constructor @@ -661,8 +662,13 @@ public class ValidationMessage implements Comparator, Compara } public String getMessage() { - return message; + return message+showCount(); } + + private String showCount() { + return count == 0 ? "" : " (also in "+count+" other files)"; + } + public ValidationMessage setMessage(String message) { this.message = message; return this; @@ -718,20 +724,20 @@ public class ValidationMessage implements Comparator, Compara } public String summary() { - return level.toString()+" @ "+location+(line>= 0 && col >= 0 ? " (line "+Integer.toString(line)+", col"+Integer.toString(col)+"): " : ": ") +message +(server != null ? " (src = "+server+")" : ""); + return level.toString()+" @ "+location+(line>= 0 && col >= 0 ? " (line "+Integer.toString(line)+", col"+Integer.toString(col)+"): " : ": ") +message+showCount() +(server != null ? " (src = "+server+")" : ""); } public String toXML() { - return "" + Utilities.escapeXml(message) + "" + html + ""; + return "" + Utilities.escapeXml(message)+showCount() + "" + html + ""; } public String getHtml() { - return html == null ? Utilities.escapeXml(message) : html; + return (html == null ? Utilities.escapeXml(message) : html)+showCount(); } public String getDisplay() { - return level + ": " + (location==null || location.isEmpty() ? "" : (location + ": ")) + message; + return level + ": " + (location==null || location.isEmpty() ? "" : (location + ": ")) + message+showCount(); } /** @@ -745,7 +751,7 @@ public class ValidationMessage implements Comparator, Compara b.append("level", level); b.append("type", type); b.append("location", location); - b.append("message", message); + b.append("message", message+showCount()); return b.build(); } @@ -953,6 +959,10 @@ public class ValidationMessage implements Comparator, Compara public void setServer(String server) { this.server = server; + } + + public void incCount() { + count++; } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 5dabcf717..3e749bcf2 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -489,7 +489,7 @@ Unable_to_handle_system__filter_with_property__ = Unable to handle system {0} fi Unable_to_resolve_system__value_set_has_include_with_no_system = Unable to resolve system - value set {0} include #{1} has no system UNABLE_TO_RESOLVE_SYSTEM_SYSTEM_IS_INDETERMINATE = The code system {1} referred to from value set {0} has a grammar, and the code might be valid in it Unable_to_resolve_system__value_set_has_include_with_unknown_system = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': include #{2} has system {3} which could not be found, and the server returned error {4} -Unable_to_resolve_system__value_set_has_include_with_filter = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': include #{2} has a filter on system {3} +Unable_to_resolve_system__value_set_has_include_with_filter = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': include #{2} has a filter on system {3}: {4} Unable_to_resolve_system__value_set_has_imports = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': value set has imports Unable_to_resolve_system__value_set_has_multiple_matches = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': value set expansion has multiple matches: {2} Unable_to_resolve_system__value_set_expansion_has_multiple_systems = The System URI could not be determined for the code ''{0}'' in the ValueSet ''{1}'': value set expansion has multiple systems @@ -1059,7 +1059,8 @@ TERMINOLOGY_TX_UNKNOWN_OID = The OID ''{0}'' is not known, so the code can't be TERMINOLOGY_TX_SYSTEM_NO_CODE = A code with no system has no defined meaning, and it cannot be validated. A system should be provided XSI_TYPE_WRONG = The xsi:type value ''{0}'' is wrong (should be ''{1}''). Note that xsi:type is unnecessary at this point XSI_TYPE_UNNECESSARY = xsi:type is unnecessary at this point -TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = The OID ''{0}'' matches multiple code systems ({1}) +TERMINOLOGY_TX_OID_MULTIPLE_MATCHES = The OID ''{0}'' matches multiple resources ({1}) +TERMINOLOGY_TX_OID_MULTIPLE_MATCHES_CHOSEN = The OID ''{0}'' matches multiple resources ({2}); {1} was chosen as the most appropriate CDA_UNKNOWN_TEMPLATE = The CDA Template {0} is not known CDA_UNKNOWN_TEMPLATE_EXT = The CDA Template {0} / {1} is not known UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = The types could not be determined from the extension context, so the invariant can't be validated (types = {0}) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index acfb0f189..67212ab6e 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -70,6 +70,8 @@ import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions; import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.context.IWorkerContext.OIDDefinition; +import org.hl7.fhir.r5.context.IWorkerContext.OIDSummary; import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; import org.hl7.fhir.r5.elementmodel.JsonParser; @@ -1777,17 +1779,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String oid = element.getNamedChildValue("codeSystem", false); if (oid != null) { - Set urls = context.urlsForOid(true, oid); - if (urls.size() != 1) { - c.setSystem("urn:oid:"+oid); - ok = false; - if (urls.size() == 0) { - warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_UNKNOWN_OID, oid); - } else { - rule(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES, oid, CommaSeparatedStringBuilder.join(",", urls)); - } + c.setSystem("urn:oid:"+oid); + OIDSummary urls = context.urlsForOid(oid, "CodeSystem"); + if (urls.urlCount() == 1) { + c.setSystem(urls.getUrl()); + } else if (urls.urlCount() == 0) { + warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_UNKNOWN_OID, oid); } else { - c.setSystem(urls.iterator().next()); + String prefUrl = urls.chooseBestUrl(); + if (prefUrl == null) { + rule(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES, oid, urls.describe()); + ok = false; + } else { + c.setSystem(prefUrl); + warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES_CHOSEN, oid, prefUrl, urls.describe()); + } } } else { warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); @@ -6588,18 +6594,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String code = element.getNamedChildValue(isPQ ? "unit" : "code", false); String oid = isPQ ? "2.16.840.1.113883.6.8" : element.getNamedChildValue("codeSystem", false); if (oid != null) { - Set urls = context.urlsForOid(true, oid); - if (urls.size() != 1) { - system = "urn:oid:"+oid; - ok = false; - - if (urls.size() == 0) { - warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_UNKNOWN_OID, oid); - } else { - rule(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES, oid, CommaSeparatedStringBuilder.join(",", urls)); - } + OIDSummary urls = context.urlsForOid(oid, "CodeSystem"); + system = "urn:oid:"+oid; + if (urls.urlCount() == 1) { + system = urls.getUrl(); + } else if (urls.urlCount() == 0) { + warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_UNKNOWN_OID, oid); } else { - system = urls.iterator().next(); + String prefUrl = urls.chooseBestUrl(); + if (prefUrl == null) { + rule(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES, oid, urls.describe()); + ok = false; + } else { + system = prefUrl; + warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_OID_MULTIPLE_MATCHES_CHOSEN, oid, prefUrl, urls.describe()); + } } } else { warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, code == null, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE); @@ -7208,19 +7217,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // todo: validate everything in this bundle. } if (rok) { - if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID, false) == null)) { - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING) && ok; - } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID, false) != null)) { - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED) && ok; - } - if (element.getNamedChild(ID, false) != null) { - Element eid = element.getNamedChild(ID, false); - if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) { - NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null); - if (eid.primitiveValue() != null && eid.primitiveValue().length() > 64) { - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MALFORMED_LENGTH, eid.primitiveValue().length()) && ok; - } else { - ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED_CHARS, eid.primitiveValue()) && ok; + // todo: not clear what we should do with regard to logical models - should they have ids? Should we check anything? + if (element.getProperty().getStructure().getKind() != StructureDefinitionKind.LOGICAL) { + if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID, false) == null)) { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING) && ok; + } else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID, false) != null)) { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED) && ok; + } + if (element.getNamedChild(ID, false) != null) { + Element eid = element.getNamedChild(ID, false); + if (eid.getProperty() != null && eid.getProperty().getDefinition() != null && eid.getProperty().getDefinition().getBase().getPath().equals("Resource.id")) { + NodeStack ns = stack.push(eid, -1, eid.getProperty().getDefinition(), null); + if (eid.primitiveValue() != null && eid.primitiveValue().length() > 64) { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MALFORMED_LENGTH, eid.primitiveValue().length()) && ok; + } else { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, eid.line(), eid.col(), ns.getLiteralPath(), FormatUtilities.isValidId(eid.primitiveValue()), I18nConstants.RESOURCE_RES_ID_MALFORMED_CHARS, eid.primitiveValue()) && ok; + } } } } @@ -7499,7 +7511,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // first case: the type value set is wrong for primitive special types for (OperationOutcomeIssueComponent iss : vr.getIssues()) { - if (iss.hasDetails() && iss.getDetails().getText().startsWith("Unable to resolve system - value set expansion has no matches for code 'http://hl7.org/fhirpath/System")) { + if (iss.hasDetails() && iss.getDetails().hasText() && iss.getDetails().getText().startsWith("Unable to resolve system - value set expansion has no matches for code 'http://hl7.org/fhirpath/System")) { return new ValidationResult("http://hl7.org/fhirpath/System", null, null, null); } }