From 771fb0ac00ce037756a7c0c08c53c5ef2fa79f50 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Fri, 9 Dec 2022 11:09:17 +1100 Subject: [PATCH] Changes related to validation of document and message links --- .../hl7/fhir/r5/utils/ToolingExtensions.java | 4 + .../fhir/utilities/i18n/I18nConstants.java | 14 +- .../src/main/resources/Messages.properties | 16 +- .../instance/type/BundleValidator.java | 209 +++++++++++++++++- .../4.0.1/all-systems.cache | 9 + 5 files changed, 236 insertions(+), 16 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 0f656a9d3..b342e5c45 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -837,6 +837,10 @@ public class ToolingExtensions { return StandardsStatus.fromCode(ToolingExtensions.readStringExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS)); } + public static StandardsStatus getStandardsStatus(Element e) throws FHIRException { + return StandardsStatus.fromCode(ToolingExtensions.readStringExtension(e, ToolingExtensions.EXT_STANDARDS_STATUS)); + } + public static void setStandardsStatus(DomainResource dr, StandardsStatus status, String normativeVersion) { if (status == null) ToolingExtensions.removeExtension(dr, ToolingExtensions.EXT_STANDARDS_STATUS); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index d848e2375..3c3808c06 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -29,8 +29,11 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE"; public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; - public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN = "Bundle_BUNDLE_Entry_Orphan"; - public static final String BUNDLE_BUNDLE_ENTRY_REVERSE = "BUNDLE_BUNDLE_ENTRY_REVERSE"; + public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT = "Bundle_BUNDLE_Entry_Orphan_DOCUMENT"; + public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE = "Bundle_BUNDLE_Entry_Orphan_MESSAGE"; + public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = "BUNDLE_BUNDLE_ENTRY_REVERSE_R4"; + public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_R5 = "BUNDLE_BUNDLE_ENTRY_REVERSE_R5"; + public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_MSG = "BUNDLE_BUNDLE_ENTRY_REVERSE_MSG"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE2 = "Bundle_BUNDLE_Entry_Type2"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE3 = "Bundle_BUNDLE_Entry_Type3"; @@ -38,6 +41,13 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_FULLURL_NEEDVERSION = "Bundle_BUNDLE_FullUrl_NeedVersion"; public static final String BUNDLE_BUNDLE_MULTIPLEMATCHES = "Bundle_BUNDLE_MultipleMatches"; public static final String BUNDLE_BUNDLE_NOT_LOCAL = "Bundle_BUNDLE_Not_Local"; + public static final String BUNDLE_LINK_UNKNOWN = "BUNDLE_LINK_UNKNOWN"; + public static final String BUNDLE_LINK_SEARCH_PROHIBITED = "BUNDLE_LINK_SEARCH_PROHIBITED"; + public static final String BUNDLE_LINK_SEARCH_NO_DUPLICATES = "BUNDLE_LINK_SEARCH_NO_DUPLICATES"; + public static final String BUNDLE_LINK_STYELSHEET_EXTERNAL = "BUNDLE_LINK_STYELSHEET_EXTERNAL"; + public static final String BUNDLE_LINK_STYELSHEET_INSECURE = "BUNDLE_LINK_STYELSHEET_INSECURE"; + public static final String BUNDLE_LINK_STYELSHEET_LINKABLE = "BUNDLE_LINK_STYELSHEET_LINKABLE"; + public static final String BUNDLE_LINK_STYELSHEET_NOT_FOUND = "BUNDLE_LINK_STYELSHEET_NOT_FOUND"; public static final String BUNDLE_MSG_EVENT_COUNT = "Bundle_MSG_Event_Count"; public static final String CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_ = "Cant_have_children_on_an_element_with_a_polymorphic_type__you_must_slice_and_constrain_the_types_first_sortElements_"; public static final String CAN_ONLY_SPECIFY_PROFILE_IN_THE_CONTEXT = "Can_only_specify_profile_in_the_context"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index dc016e541..23079b76a 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -12,8 +12,11 @@ Bundle_BUNDLE_Entry_NoProfile_TYPE = No profile found for {0} resource of type ' Bundle_BUNDLE_Entry_NoProfile_EXPL = Specified profile {2} not found for {0} resource of type ''{0}'' Bundle_BUNDLE_Entry_NO_LOGICAL_EXPL = Specified logical model {1} not found for resource ''Binary/{0}'' Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1}) -Bundle_BUNDLE_Entry_Orphan = Entry {0} isn''t reachable by traversing from first Bundle entry -BUNDLE_BUNDLE_ENTRY_REVERSE = Entry {0} isn''t reachable by traversing forwards from first Bundle entry, and isn''t a resource type that is typically used that way - check this is not missed somewhere +Bundle_BUNDLE_Entry_Orphan_MESSAGE = Entry {0} isn''t reachable by traversing links (forward or backward) from the MessageHeader, so it's presence should be reviewed (is it needed to process the message?) +Bundle_BUNDLE_Entry_Orphan_DOCUMENT = Entry {0} isn''t reachable by traversing links (forward or backward) from the Composition +BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = Entry {0} isn''t reachable by traversing forwards from the Composition. Only Provenance is approved to be used this way (R4 section 3.3.1) +BUNDLE_BUNDLE_ENTRY_REVERSE_R5 = Entry {0} isn''t reachable by traversing forwards from the Composition. Check whether this should be linked directly from the composition if it's a source of narrative content +BUNDLE_BUNDLE_ENTRY_REVERSE_MSG = Entry {0} isn''t reachable by traversing forwards from the MessageHeader. Check that this is meant to be included (needed to process the message) Bundle_BUNDLE_Entry_Type = The type ''{0}'' is not valid - no resources allowed here (allowed = {1}) Bundle_BUNDLE_Entry_Type2 = The type ''{0}'' is not valid - must be {1} (allowed = {2}) Bundle_BUNDLE_Entry_Type3_one = The type ''{1}'' is not valid - must be of type {2} @@ -23,7 +26,7 @@ Bundle_BUNDLE_FullUrl_NeedVersion = Entries matching fullURL {0} should declare Bundle_BUNDLE_MultipleMatches = Multiple matches in bundle for reference {0} Bundle_BUNDLE_Not_Local = URN reference is not locally contained within the bundle {0} Bundle_MSG_Event_Count = Expected {0} but found {1} event elements -Bundle_Document_Date_Missing = A document must have a date +Bundle_Document_Date_Missing = A document must have a date (Bundle.timestamp) Bundle_Document_Date_Missing_html = [(type = ''document'') implies (meta.lastUpdated.hasValue())] CapabalityStatement_CS_SP_WrongType = Type mismatch - SearchParameter ''{0}'' type is {1}, but type here is {2} CodeSystem_CS_VS_IncludeDetails = CodeSystem {0} has an ''all system'' value set of {1}, but the include has extra details @@ -807,3 +810,10 @@ JSON_PROPERTY_VALUE_NO_QUOTES = The JSON property ''{0}'' has no quotes around t JSON_COMMA_MISSING = A Comma is missing in the JSON JSON_COMMA_EXTRA = There is an extra comma at the end of the {0} in the JSON JSON_COMMENTS_NOT_ALLOWED = Comments are not allowed in JSON +BUNDLE_LINK_UNKNOWN = The link relationship type ''{0}'' is unknown and not allowed in this context +BUNDLE_LINK_SEARCH_PROHIBITED = The link relationship type ''{0}'' used in search sets is prohibited in this context +BUNDLE_LINK_SEARCH_NO_DUPLICATES = The link relationship type ''{0}'' can only occur once +BUNDLE_LINK_STYELSHEET_EXTERNAL = External Stylesheets other than https://hl7.org/fhir/fhir.css SHOULD not be used +BUNDLE_LINK_STYELSHEET_INSECURE = The stylesheet reference is not secure +BUNDLE_LINK_STYELSHEET_LINKABLE = The stylesheet reference is not resolvable link +BUNDLE_LINK_STYELSHEET_NOT_FOUND = The stylesheet reference could not be resolved in this bundle diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java index b4871d79b..6428fe9ce 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java @@ -44,11 +44,20 @@ public class BundleValidator extends BaseValidator { public boolean validateBundle(List errors, Element bundle, NodeStack stack, boolean checkSpecials, ValidatorHostContext hostContext, PercentageTracker pct, ValidationMode mode) { boolean ok = true; - List entries = new ArrayList(); - bundle.getNamedChildren(ENTRY, entries); String type = bundle.getNamedChildValue(TYPE); type = StringUtils.defaultString(type); + List entries = new ArrayList(); + bundle.getNamedChildren(ENTRY, entries); + List links = new ArrayList(); + bundle.getNamedChildren(LINK, links); + if (links.size() > 0) { + int i = 0; + for (Element l : links) { + ok = validateLink(errors, bundle, links, l, stack.push(l, i++, null, null), type, entries) && ok; + } + } + if (entries.size() == 0) { ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST) && ok; } else { @@ -68,7 +77,7 @@ public class BundleValidator extends BaseValidator { if (!VersionUtilities.isThisOrLater(FHIRVersion._4_0_1.getDisplay(), bundle.getProperty().getStructure().getFhirVersion().getDisplay())) { ok = handleSpecialCaseForLastUpdated(bundle, errors, stack) && ok; } - ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok; + ok = checkAllInterlinked(errors, entries, stack, bundle, false) && ok; } if (type.equals(MESSAGE)) { Element resource = firstEntry.getNamedChild(RESOURCE); @@ -76,7 +85,7 @@ public class BundleValidator extends BaseValidator { if (rule(errors, NO_RULE_DATE, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { ok = validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id) && ok; } - ok = checkAllInterlinked(errors, entries, stack, bundle, VersionUtilities.isR5Ver(context.getVersion())) && ok; + ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok; } if (type.equals(SEARCHSET)) { checkSearchSet(errors, bundle, entries, stack); @@ -133,6 +142,151 @@ public class BundleValidator extends BaseValidator { return ok; } + private boolean validateLink(List errors, Element bundle, List links, Element link, NodeStack stack, String type, List entries) { + switch (type) { + case "document": return validateDocumentLink(errors, bundle, links, link, stack, entries); + case "message": return validateMessageLink(errors, bundle, links, link, stack, entries); + case "history": + case "searchset": return validateSearchLink(errors, bundle, links, link, stack); + case "collection": return validateCollectionLink(errors, bundle, links, link, stack); + case "subscription-notification": return validateSubscriptionLink(errors, bundle, links, link, stack); + case "transaction": + case "transaction-response": + case "batch": + case "batch-response": + return validateTransactionOrBatchLink(errors, bundle, links, link, stack); + default: + return true; // unknown document type, deal with that elsewhere + } +// rule(errors, "2022-12-09", IssueType.INVALID, l.line(), l.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_UNKNOWN, ); + } + + private boolean validateDocumentLink(List errors, Element bundle, List links, Element link, NodeStack stack, List entries) { + boolean ok = true; + Element relE = link.getNamedChild("relation"); + if (relE != null) { + NodeStack relStack = stack.push(relE, -1, null, null); + String rel = relE.getValue(); + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel); + if ("self".equals(rel)) { + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok; + } + if ("stylesheet".equals(rel)) { + Element urlE = link.getNamedChild("url"); + if (urlE != null) { + NodeStack urlStack = stack.push(urlE, -1, null, null); + String url = urlE.getValue(); + if (url != null) { + if (Utilities.isAbsoluteUrl(url)) { + // todo: do we need to consider rel = base? + if (url.equals("https://hl7.org/fhir/fhir.css")) { + // well, this is ok! + } else { + warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_EXTERNAL); + if (url.startsWith("http://")) { + warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_INSECURE); + } + if (!Utilities.isAbsoluteUrlLinkable(url)) { + warning(errors, "2022-12-09", IssueType.BUSINESSRULE, urlE.line(), urlE.col(), urlStack.getLiteralPath(), false, I18nConstants.BUNDLE_LINK_STYELSHEET_LINKABLE); + } + } + } else { + // has to resolve in the bundle + boolean found = false; + for (Element e : entries) { + Element res = e.getNamedChild("resource"); + if (res != null && (""+res.fhirType()+"/"+res.getIdBase()).equals(url)) { + found = true; + break; + } + } + ok = rule(errors, "2022-12-09", IssueType.NOTFOUND, urlE.line(), urlE.col(), urlStack.getLiteralPath(), found, I18nConstants.BUNDLE_LINK_STYELSHEET_NOT_FOUND) && ok; + } + } + } + } + } + return ok; + } + + private boolean validateMessageLink(List errors, Element bundle, List links, Element link, NodeStack stack, List entries) { + boolean ok = true; + Element relE = link.getNamedChild("relation"); + if (relE != null) { + NodeStack relStack = stack.push(relE, -1, null, null); + String rel = relE.getValue(); + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel); + if ("self".equals(rel)) { + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok; + } + } + return ok; + } + + private boolean validateSearchLink(List errors, Element bundle, List links, Element link, NodeStack stack) { + String rel = StringUtils.defaultString(link.getNamedChildValue("relation")); + if (Utilities.existsInList(rel, "first", "previous", "next", "last", "self")) { + return rule(errors, "2022-12-09", IssueType.INVALID, link.line(), link.col(), stack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel); + } else { + return true; + } + } + + private boolean relationshipUnique(String rel, Element link, List links) { + for (Element l : links) { + if (l != link && rel.equals(l.getNamedChildValue("relation"))) { + return false; + } + if (l == link) { + // we only want to complain once, so we only look above this one + return true; + } + } + return true; + } + + private boolean validateCollectionLink(List errors, Element bundle, List links, Element link, NodeStack stack) { + boolean ok = true; + Element relE = link.getNamedChild("relation"); + if (relE != null) { + NodeStack relStack = stack.push(relE, -1, null, null); + String rel = relE.getValue(); + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel); + if ("self".equals(rel)) { + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok; + } + } + return ok; + } + + private boolean validateSubscriptionLink(List errors, Element bundle, List links, Element link, NodeStack stack) { + boolean ok = true; + Element relE = link.getNamedChild("relation"); + if (relE != null) { + NodeStack relStack = stack.push(relE, -1, null, null); + String rel = relE.getValue(); + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel); + if ("self".equals(rel)) { + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok; + } + } + return ok; + } + + private boolean validateTransactionOrBatchLink(List errors, Element bundle, List links, Element link, NodeStack stack) { + boolean ok = true; + Element relE = link.getNamedChild("relation"); + if (relE != null) { + NodeStack relStack = stack.push(relE, -1, null, null); + String rel = relE.getValue(); + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), !Utilities.existsInList(rel, "first", "previous", "next", "last"), I18nConstants.BUNDLE_LINK_SEARCH_PROHIBITED, rel); + if ("self".equals(rel)) { + ok = rule(errors, "2022-12-09", IssueType.INVALID, relE.line(), relE.col(), relStack.getLiteralPath(), relationshipUnique(rel, link, links), I18nConstants.BUNDLE_LINK_SEARCH_NO_DUPLICATES, rel) && ok; + } + } + return ok; + } + private void checkSearchSet(List errors, Element bundle, List entries, NodeStack stack) { // warning: should have self link List links = new ArrayList(); @@ -413,7 +567,7 @@ public class BundleValidator extends BaseValidator { return ok; } - private boolean checkAllInterlinked(List errors, List entries, NodeStack stack, Element bundle, boolean isError) { + private boolean checkAllInterlinked(List errors, List entries, NodeStack stack, Element bundle, boolean isMessage) { boolean ok = true; List entryList = new ArrayList<>(); int i = 0; @@ -447,6 +601,7 @@ public class BundleValidator extends BaseValidator { Set visited = new HashSet<>(); visitLinked(visited, entryList.get(0)); + visitBundleLinks(visited, entryList, bundle); boolean foundRevLinks; do { foundRevLinks = false; @@ -460,10 +615,22 @@ public class BundleValidator extends BaseValidator { } } if (add) { - warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(), + if (isMessage) { + hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(), + stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()), + I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_MSG, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : "")); + } else { + // this was illegal up to R4B, but changed to be legal in R5 + if (VersionUtilities.isR5VerOrLater(context.getVersion())) { + hint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, e.getEntry().line(), e.getEntry().col(), + stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()), + I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_R4, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : "")); + } else { + warning(errors, NO_RULE_DATE, IssueType.INVALID, e.getEntry().line(), e.getEntry().col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), isExpectedToBeReverse(e.getResource().fhirType()), - I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : "")); -// System.out.println("Found reverse links for "+e.getIndex()); + I18nConstants.BUNDLE_BUNDLE_ENTRY_REVERSE_R4, (e.getEntry().getChildValue(FULL_URL) != null ? "'" + e.getEntry().getChildValue(FULL_URL) + "'" : "")); + } + } foundRevLinks = true; visitLinked(visited, e); } @@ -474,10 +641,10 @@ public class BundleValidator extends BaseValidator { i = 0; for (EntrySummary e : entryList) { Element entry = e.getEntry(); - if (isError) { - ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")) && ok; + if (isMessage) { + warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")); } else { - warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")); + ok = rule(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT, (entry.getChildValue(FULL_URL) != null ? "'" + entry.getChildValue(FULL_URL) + "'" : "")) && ok; } i++; } @@ -486,6 +653,26 @@ public class BundleValidator extends BaseValidator { + private void visitBundleLinks(Set visited, List entryList, Element bundle) { + List links = bundle.getChildrenByName("link"); + for (Element link : links) { + String rel = link.getNamedChildValue("relation"); + String url = link.getNamedChildValue("url"); + if (rel != null && url != null) { + if (Utilities.existsInList(rel, "stylesheet")) { + for (EntrySummary e : entryList) { + if (e.getResource() != null) { + if (url.equals(e.getResource().fhirType()+"/"+e.getResource().getIdBase())) { + visited.add(e); + break; + } + } + } + } + } + } + } + private boolean isExpectedToBeReverse(String fhirType) { return Utilities.existsInList(fhirType, "Provenance"); } diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache index f7d91d93e..108ef1ec5 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache @@ -2068,3 +2068,12 @@ v: { "system" : "http://snomed.info/sct" } ------------------------------------------------------------------------------------- +{"code" : { + "code" : "text/css" +}, "url": "http://hl7.org/fhir/ValueSet/mimetypes", "version": "4.0.1", "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"true", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}#### +v: { + "display" : "text/css", + "code" : "text/css", + "system" : "urn:ietf:bcp:13" +} +-------------------------------------------------------------------------------------