From 130cc3892cc25c11c22f40d8592182619266eec4 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 10 Mar 2020 17:57:31 +1100 Subject: [PATCH 1/5] remove dead code --- .../java/org/hl7/fhir/validation/instance/InstanceValidator.java | 1 - 1 file changed, 1 deletion(-) 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 75b782929..2cff601ae 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 @@ -3335,7 +3335,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials()); - List res = new ArrayList<>(); Element meta = element.getNamedChild("meta"); if (meta != null) { List profiles = new ArrayList(); From bacaf09af6fb3d060fa4c3137f475139482b9203 Mon Sep 17 00:00:00 2001 From: patrick-werner Date: Wed, 11 Mar 2020 00:46:09 +0100 Subject: [PATCH 2/5] added -locale cli option --- .../org/hl7/fhir/r5/context/BaseWorkerContext.java | 2 +- .../org/hl7/fhir/r5/utils/IResourceValidator.java | 5 ++++- .../org/hl7/fhir/validation/ValidationEngine.java | 9 +++++++++ .../main/java/org/hl7/fhir/validation/Validator.java | 11 +++++++++++ .../fhir/validation/tests/ValidationTestSuite.java | 5 +++++ 5 files changed, 30 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java index 8b75abbcc..aed8018b4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/BaseWorkerContext.java @@ -1311,6 +1311,6 @@ public abstract class BaseWorkerContext implements IWorkerContext { @Override public void setValidationMessageLanguage(Locale locale) { - i18Nmessages = ResourceBundle.getBundle("Messages", locale ); + i18Nmessages = ResourceBundle.getBundle("Messages", locale); } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java index 8f2ec973c..bc99d7a0b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/IResourceValidator.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; +import java.util.Locale; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; @@ -68,7 +69,9 @@ public interface IResourceValidator { Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException; ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url); - boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException; + boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException; + + void setLocale(Locale locale); } public enum BestPracticeWarningLevel { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 0e8d93ecc..3e5a1d91d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -139,6 +139,8 @@ POSSIBILITY OF SUCH DAMAGE. */ public class ValidationEngine implements IValidatorResourceFetcher { + + public class ScanOutputItem { private String ref; private ImplementationGuide ig; @@ -240,6 +242,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { private Set loadedIgs = new HashSet<>(); private IValidatorResourceFetcher fetcher; private boolean assumeValidRestReferences; + private Locale locale; private class AsteriskFilter implements FilenameFilter { String dir; @@ -1269,6 +1272,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { validator.setNoInvariantChecks(isNoInvariantChecks()); validator.setValidationLanguage(language); validator.setAssumeValidRestReferences(assumeValidRestReferences); + validator.getContext().setLocale(locale); validator.setFetcher(this); return validator; } @@ -1592,6 +1596,11 @@ public class ValidationEngine implements IValidatorResourceFetcher { return false; } + @Override + public void setLocale(Locale locale) { + this.locale = locale; + } + public void handleOutput(Resource r, String output, String version) throws Exception { if (output.startsWith("http://") || output.startsWith("http://")) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java index 408ed8739..e69c96e96 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Validator.java @@ -55,6 +55,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -195,6 +196,8 @@ public class Validator { System.out.println(" Produce additional information about the loading/validation process"); System.out.println("-recurse"); System.out.println(" Look in subfolders when -ig refers to a folder"); + System.out.println("-locale"); + System.out.println(" Specifies the locale/language of the validation result messages (eg.: de-DE"); System.out.println("-sct"); System.out.println(" Specify the edition of SNOMED CT to use. Valid Choices:"); System.out.println(" intl | us | uk | au | nl | ca | se | dk | es"); @@ -399,6 +402,7 @@ public class Validator { boolean anyExtensionsAllowed = true; boolean hintAboutNonMustSupport = false; boolean recursive = false; + Locale locale = null; List profiles = new ArrayList(); EngineMode mode = EngineMode.VALIDATION; String output = null; @@ -477,6 +481,12 @@ public class Validator { throw new Error("Snomed edition '"+s+"' not known"); } else if (args[i].equals("-recurse")) { recursive = true; + } else if (args[i].equals("-locale")) { + if (i+1 == args.length) { + throw new Error("Specified -locale without indicating locale"); + } else { + locale = new Locale(args[++i]); + } } else if (args[i].equals("-strictExtensions")) { anyExtensionsAllowed = false; } else if (args[i].equals("-hintAboutNonMustSupport")) { @@ -585,6 +595,7 @@ public class Validator { validator.setHintAboutNonMustSupport(hintAboutNonMustSupport); validator.setAnyExtensionsAllowed(anyExtensionsAllowed); validator.setLanguage(lang); + validator.setLocale(locale); validator.setSnomedExtension(snomedCT); validator.setAssumeValidRestReferences(assumeValidRestReferences); 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 b89b5f5d5..dd95b31f8 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 @@ -426,6 +426,11 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour return !url.contains("example.org"); } + @Override + public void setLocale(Locale locale) { + //do nothing + } + @Override public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { IResourceValidator val = TestingUtilities.context(version).newValidator(); From a1e16a74360721c72d4c050cc0aaca7f039e1090 Mon Sep 17 00:00:00 2001 From: Patrick Werner Date: Wed, 11 Mar 2020 01:12:16 +0100 Subject: [PATCH 3/5] Update Messages_de_DE.properties --- .../src/main/resources/Messages_de_DE.properties | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties b/org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties index 3ede37a93..bbfbb87fd 100644 --- a/org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties +++ b/org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties @@ -10,8 +10,7 @@ Bundle_BUNDLE_Entry_NoProfile=Kein Profil für die contained Ressource vom Typ " Bundle_BUNDLE_Entry_NotFound="{0}" konnte nicht in bundle ({1}) gefunden werden Bundle_BUNDLE_Entry_Orphan=Entry {0} ist nicht durch Traversierung, vom ersten Bundle-Entry ausgehend, erreichbar Bundle_BUNDLE_Entry_Type=Der type "{0}" ist nicht gültig - hier sind keine Ressourcen erlaubt -Bundle_BUNDLE_Entry_Type2=Der type "{0}" ist nicht gültig - muss {1} sein\ - +Bundle_BUNDLE_Entry_Type2=Der type "{0}" ist nicht gültig - muss {1} sein Bundle_BUNDLE_Entry_Type3=Der type "{0}" ist nicht gültig - muss einer von {1} sein Bundle_BUNDLE_FullUrl_Missing=Es besteht eine relative Reference innerhalb des Bundle, dessen Eintrag eine fullUrl fehlt Bundle_BUNDLE_FullUrl_NeedVersion=Einträge, die mit fullURL {0} übereinstimmen, sollten meta/versionId deklarieren, da versionsspezifische Referenzen vorhanden sind. From a4a8d4cee2f5e02f61d3b7e590bb5d3c895e6790 Mon Sep 17 00:00:00 2001 From: patrick-werner Date: Wed, 11 Mar 2020 01:13:36 +0100 Subject: [PATCH 4/5] renamed Message_de properties --- .../{Messages_de_DE.properties => Messages_de.properties} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename org.hl7.fhir.validation/src/main/resources/{Messages_de_DE.properties => Messages_de.properties} (100%) diff --git a/org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties b/org.hl7.fhir.validation/src/main/resources/Messages_de.properties similarity index 100% rename from org.hl7.fhir.validation/src/main/resources/Messages_de_DE.properties rename to org.hl7.fhir.validation/src/main/resources/Messages_de.properties From a097ca0e673423f0708f16b795759906562c5d66 Mon Sep 17 00:00:00 2001 From: markiantorno Date: Wed, 11 Mar 2020 16:09:23 -0400 Subject: [PATCH 5/5] Updated to check for Bundle.meta.lastUpdated in document bundles. Rules for this are listed here: http://hl7.org/fhir/stu3/documents.html#content This is related to an issue with documentation clarity (FHIR-26544) https://jira.hl7.org/browse/FHIR-26544 Also, in this ticket, I have taken some of the string variables used multiple times throughout the methods I touched and put them into final fields. --- .../instance/InstanceValidator.java | 194 +++++++++++------- .../validation/tests/ValidationTestSuite.java | 3 +- pom.xml | 2 +- 3 files changed, 118 insertions(+), 81 deletions(-) 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 75b782929..bbcaed9a5 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 @@ -167,6 +167,18 @@ import ca.uhn.fhir.util.ObjectUtil; public class InstanceValidator extends BaseValidator implements IResourceValidator { + private final String META = "meta"; + private final String ENTRY = "entry"; + private final String DOCUMENT = "document"; + private final String RESOURCE = "resource"; + private final String MESSAGE = "message"; + private final String ID = "id"; + private final String PATH_ARG = ":0"; + private final String FULL_URL = "fullUrl"; + private final String TYPE = "type"; + private final String BUNDLE = "Bundle"; + private final String LAST_UPDATED = "lastUpdated"; + private class ValidatorHostServices implements IEvaluationContext { @Override @@ -247,13 +259,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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 (bnd.fhirType().equals(BUNDLE)) { + for (Element be : bnd.getChildrenByName(ENTRY)) { + Element res = be.getNamedChild(RESOURCE); if (res != null) { - String fullUrl = be.getChildValue("fullUrl"); + String fullUrl = be.getChildValue(FULL_URL); String rt = res.fhirType(); - String id = res.getChildValue("id"); + String id = res.getChildValue(ID); if (url.equals(fullUrl)) return res; if (url.equals(rt + "/" + id)) @@ -1739,7 +1751,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat 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 + ".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); @@ -1802,7 +1814,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); } } - if (type.equals("id")) { + 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()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_ID_VALID, e.primitiveValue()); @@ -1971,7 +1983,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, 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", + "title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex", // tables "span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") || @@ -2440,7 +2452,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat container.getNamedChildren("contained", contained); for (int i = 0; i < contained.size(); i++) { Element we = contained.get(i); - if (id.equals(we.getNamedChildValue("id"))) { + if (id.equals(we.getNamedChildValue(ID))) { return new IndexedElement(i, we, null); } } @@ -2574,26 +2586,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } List entries = new ArrayList(); - bundle.getNamedChildren("entry", entries); + 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 (targetUrl.equals(we.getChildValue(FULL_URL))) { + Element r = we.getNamedChild(RESOURCE); if (version.isEmpty()) { rule(errors, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref); match = r; matchIndex = i; } else { try { - if (version.equals(r.getChildren("meta").get(0).getChildValue("versionId"))) { + if (version.equals(r.getChildren(META).get(0).getChildValue("versionId"))) { rule(errors, IssueType.FORBIDDEN, -1, -1, path, match == null, I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ref); match = r; matchIndex = i; } } catch (Exception e) { - warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren("meta").size() == 1 && r.getChildren("meta").get(0).getChildValue("versionId") != null, I18nConstants.BUNDLE_BUNDLE_FULLURL_NEEDVERSION, targetUrl); + warning(errors, IssueType.REQUIRED, -1, -1, path, r.getChildren(META).size() == 1 && r.getChildren(META).get(0).getChildValue("versionId") != null, I18nConstants.BUNDLE_BUNDLE_FULLURL_NEEDVERSION, targetUrl); // If one of these things is null } } @@ -2703,12 +2715,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean isParametersEntry(String path) { String[] parts = path.split("\\."); - return parts.length > 2 && parts[parts.length - 1].equals("resource") && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part")); + return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && (pathEntryHasName(parts[parts.length - 2], "parameter") || pathEntryHasName(parts[parts.length - 2], "part")); } private boolean isBundleEntry(String path) { String[] parts = path.split("\\."); - return parts.length > 2 && parts[parts.length - 1].equals("resource") && pathEntryHasName(parts[parts.length - 2], "entry"); + return parts.length > 2 && parts[parts.length - 1].equals(RESOURCE) && pathEntryHasName(parts[parts.length - 2], ENTRY); } private boolean isBundleOutcome(String path) { @@ -2798,15 +2810,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // the resource in the bundle String fullUrl = null; // we're going to try to work this out as we go up while (stack != null && stack.getElement() != null) { - if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.parent != null && stack.parent.getElement().getName().equals("entry")) { - String type = stack.parent.parent.element.getChildValue("type"); - fullUrl = stack.parent.getElement().getChildValue("fullUrl"); // we don't try to resolve contained references across this boundary + if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY && fullUrl == null && stack.parent != null && stack.parent.getElement().getName().equals(ENTRY)) { + String type = stack.parent.parent.element.getChildValue(TYPE); + fullUrl = stack.parent.getElement().getChildValue(FULL_URL); // we don't try to resolve contained references across this boundary if (fullUrl == null) rule(errors, IssueType.REQUIRED, stack.parent.getElement().line(), stack.parent.getElement().col(), stack.parent.getLiteralPath(), Utilities.existsInList(type, "batch-response", "transaction-response") || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFULLURL); } - if ("Bundle".equals(stack.getElement().getType())) { - String type = stack.getElement().getChildValue("type"); + if (BUNDLE.equals(stack.getElement().getType())) { + String type = stack.getElement().getChildValue(TYPE); IndexedElement res = getFromBundle(stack.getElement(), ref, fullUrl, errors, path, type, "transaction".equals(type)); if (res == null) { return null; @@ -2824,10 +2836,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat stack = stack.parent; } // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. - if (hostContext != null && "Bundle".equals(hostContext.fhirType())) { - String type = hostContext.getChildValue("type"); + if (hostContext != null && BUNDLE.equals(hostContext.fhirType())) { + String type = hostContext.getChildValue(TYPE); Element entry = getEntryForSource(hostContext, source); - fullUrl = entry.getChildValue("fullUrl"); + fullUrl = entry.getChildValue(FULL_URL); IndexedElement res = getFromBundle(hostContext, ref, fullUrl, errors, path, type, "transaction".equals(type)); if (res == null) { return null; @@ -2848,7 +2860,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private Element getEntryForSource(Element bundle, Element element) { List entries = new ArrayList(); - bundle.getNamedChildren("entry", entries); + bundle.getNamedChildren(ENTRY, entries); for (Element entry : entries) { if (entry.hasDescendant(element)) { return entry; @@ -2930,7 +2942,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (Utilities.isAbsoluteUrl(ref)) { // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. for (Element entry : entries) { - String fu = entry.getNamedChildValue("fullUrl"); + String fu = entry.getNamedChildValue(FULL_URL); if (ref.equals(fu)) return entry; } @@ -2947,14 +2959,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String t = parts[0]; String i = parts[1]; for (Element entry : entries) { - String fu = entry.getNamedChildValue("fullUrl"); + String fu = entry.getNamedChildValue(FULL_URL); if (fu != null && fu.equals(u)) return entry; if (u == null) { - Element resource = entry.getNamedChild("resource"); + Element resource = entry.getNamedChild(RESOURCE); if (resource != null) { String et = resource.getType(); - String eid = resource.getNamedChildValue("id"); + String eid = resource.getNamedChildValue(ID); if (t.equals(et) && i.equals(eid)) return entry; } @@ -3218,7 +3230,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat first = false; else expression.append(" and "); - buildCodeableConceptExpression(ed, expression, "type", ii.getType()); + buildCodeableConceptExpression(ed, expression, TYPE, ii.getType()); } expression.append(").exists()"); } @@ -3330,13 +3342,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void start(ValidatorHostContext hostContext, List errors, Element resource, Element element, StructureDefinition defn, NodeStack stack) throws FHIRException { checkLang(resource, stack); - if ("Bundle".equals(element.fhirType())) { + if (BUNDLE.equals(element.fhirType())) { resolveBundleReferences(element, new ArrayList()); } startInner(hostContext, errors, resource, element, defn, stack, hostContext.isCheckSpecials()); List res = new ArrayList<>(); - Element meta = element.getNamedChild("meta"); + Element meta = element.getNamedChild(META); if (meta != null) { List profiles = new ArrayList(); meta.getNamedChildren("profile", profiles); @@ -3359,10 +3371,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat List list = new ArrayList(); list.addAll(bundles); list.add(0, element); - List entries = element.getChildrenByName("entry"); + List entries = element.getChildrenByName(ENTRY); for (Element entry : entries) { - String fu = entry.getChildValue("fullUrl"); - Element r = entry.getNamedChild("resource"); + String fu = entry.getChildValue(FULL_URL); + Element r = entry.getNamedChild(RESOURCE); if (r != null) { resolveBundleReferencesInResource(list, r, fu); } @@ -3372,7 +3384,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void resolveBundleReferencesInResource(List bundles, Element r, String fu) { r.setUserData("validator.bundle.resolution-resource", null); - if ("Bundle".equals(r.fhirType())) { + if (BUNDLE.equals(r.fhirType())) { resolveBundleReferences(r, bundles); } else { for (Element child : r.getChildren()) { @@ -3386,10 +3398,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String ref = element.getChildValue("reference"); if (!Utilities.noString(ref)) { for (Element bundle : bundles) { - List entries = bundle.getChildren("entry"); + List entries = bundle.getChildren(ENTRY); Element tgt = resolveInBundle(entries, ref, fu, resource.fhirType(), resource.getIdBase()); if (tgt != null) { - element.setUserData("validator.bundle.resolution", tgt.getNamedChild("resource")); + element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE)); return; } } @@ -3437,7 +3449,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public void checkSpecials(ValidatorHostContext hostContext, List errors, Element element, NodeStack stack, boolean checkSpecials) { // specific known special validations - if (element.getType().equals("Bundle")) { + if (element.getType().equals(BUNDLE)) { validateBundle(errors, element, stack, checkSpecials); } else if (element.getType().equals("Observation")) { validateObservation(errors, element, stack); @@ -3586,7 +3598,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } } // security tags are a set (system|code) - Element meta = element.getNamedChild("meta"); + Element meta = element.getNamedChild(META); if (meta != null) { Set tags = new HashSet<>(); List list = new ArrayList<>(); @@ -3605,11 +3617,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat int iRest = 0; for (Element rest : cs.getChildrenByName("rest")) { int iResource = 0; - for (Element resource : rest.getChildrenByName("resource")) { + for (Element resource : rest.getChildrenByName(RESOURCE)) { int iSP = 0; for (Element searchParam : resource.getChildrenByName("searchParam")) { String ref = searchParam.getChildValue("definition"); - String type = searchParam.getChildValue("type"); + String type = searchParam.getChildValue(TYPE); if (!Utilities.noString(ref)) { SearchParameter sp = context.fetchResource(SearchParameter.class, ref); if (sp != null) { @@ -4213,32 +4225,35 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void validateBundle(List errors, Element bundle, NodeStack stack, boolean checkSpecials) { List entries = new ArrayList(); - bundle.getNamedChildren("entry", entries); - String type = bundle.getNamedChildValue("type"); + bundle.getNamedChildren(ENTRY, entries); + String type = bundle.getNamedChildValue(TYPE); type = StringUtils.defaultString(type); if (entries.size() == 0) { - rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals("document") || type.equals("message")), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST); + rule(errors, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST); } else { // Get the first entry, the MessageHeader Element firstEntry = entries.get(0); // Get the stack of the first entry NodeStack firstStack = stack.push(firstEntry, 1, null, null); - String fullUrl = firstEntry.getNamedChildValue("fullUrl"); + String fullUrl = firstEntry.getNamedChildValue(FULL_URL); - if (type.equals("document")) { - Element resource = firstEntry.getNamedChild("resource"); - String id = resource.getNamedChildValue("id"); - if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { + if (type.equals(DOCUMENT)) { + Element resource = firstEntry.getNamedChild(RESOURCE); + String id = resource.getNamedChildValue(ID); + if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { validateDocument(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id); } + if (!VersionUtilities.isThisOrLater(FHIRVersion._4_0_1.getDisplay(), bundle.getProperty().getStructure().getFhirVersion().getDisplay())) { + handleSpecialCaseForLastUpdated(bundle, errors, stack); + } checkAllInterlinked(errors, entries, stack, bundle, true); } - if (type.equals("message")) { - Element resource = firstEntry.getNamedChild("resource"); - String id = resource.getNamedChildValue("id"); - if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath("entry", ":0"), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { + if (type.equals(MESSAGE)) { + Element resource = firstEntry.getNamedChild(RESOURCE); + String id = resource.getNamedChildValue(ID); + if (rule(errors, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id); } checkAllInterlinked(errors, entries, stack, bundle, VersionUtilities.isR5Ver(context.getVersion())); @@ -4247,18 +4262,39 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // validateResourceIds(errors, entries, stack); } for (Element entry : entries) { - String fullUrl = entry.getNamedChildValue("fullUrl"); + String fullUrl = entry.getNamedChildValue(FULL_URL); String url = getCanonicalURLForEntry(entry); String id = getIdForEntry(entry); if (url != null) { if (!(!url.equals(fullUrl) || (url.matches(uriRegexForVersion()) && url.endsWith("/" + id))) && !isV3orV2Url(url)) - rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MISMATCHIDURL, url, fullUrl, id); - rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath("entry", ":0"), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild("resource").fhirType(), id))), I18nConstants.BUNDLE_BUNDLE_ENTRY_CANONICAL, url, fullUrl); + rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MISMATCHIDURL, url, fullUrl, id); + rule(errors, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild(RESOURCE).fhirType(), id))), I18nConstants.BUNDLE_BUNDLE_ENTRY_CANONICAL, url, fullUrl); } // todo: check specials } } + /** + * As per outline for Document Content: + *
  • "The document date (mandatory). This is found in Bundle.meta.lastUpdated and identifies when the document bundle + * was assembled from the underlying resources"
  • + *

    + * This check was not being done for release versions < r4. + *

    + * Related JIRA ticket is FHIR-26544 + * + * @param bundle {@link org.hl7.fhir.r5.elementmodel} + * @param errors {@link List} + * @param stack {@link NodeStack} + */ + private void handleSpecialCaseForLastUpdated(Element bundle, List errors, NodeStack stack) { + boolean ok = bundle.hasChild(META) + && bundle.getNamedChild(META).hasChild(LAST_UPDATED) + && bundle.getNamedChild(META).getNamedChild(LAST_UPDATED).hasValue(); + rule(errors, IssueType.REQUIRED, stack.addToLiteralPath(ENTRY, PATH_ARG), ok, "A document must have a date [(type = 'document') implies (meta.lastUpdated.hasValue())]"); + } + + // hack for pre-UTG v2/v3 private boolean isV3orV2Url(String url) { return url.startsWith("http://hl7.org/fhir/v3/") || url.startsWith("http://hl7.org/fhir/v2/"); @@ -4276,17 +4312,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private String getCanonicalURLForEntry(Element entry) { - Element e = entry.getNamedChild("resource"); + Element e = entry.getNamedChild(RESOURCE); if (e == null) return null; return e.getNamedChildValue("url"); } private String getIdForEntry(Element entry) { - Element e = entry.getNamedChild("resource"); + Element e = entry.getNamedChild(RESOURCE); if (e == null) return null; - return e.getNamedChildValue("id"); + return e.getNamedChildValue(ID); } /** @@ -4302,9 +4338,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // TODO: Need to handle _version int i = 1; for (Element entry : entries) { - String fullUrl = entry.getNamedChildValue("fullUrl"); - Element resource = entry.getNamedChild("resource"); - String id = resource != null ? resource.getNamedChildValue("id") : null; + String fullUrl = entry.getNamedChildValue(FULL_URL); + Element resource = entry.getNamedChild(RESOURCE); + String id = resource != null ? resource.getNamedChildValue(ID) : null; if (id != null && fullUrl != null) { String urlId = null; if (fullUrl.startsWith("https://") || fullUrl.startsWith("http://")) { @@ -4321,7 +4357,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private void checkAllInterlinked(List errors, List entries, NodeStack stack, Element bundle, boolean isError) { List entryList = new ArrayList<>(); for (Element entry : entries) { - Element r = entry.getNamedChild("resource"); + Element r = entry.getNamedChild(RESOURCE); if (r != null) { entryList.add(new EntrySummary(entry, r)); } @@ -4329,7 +4365,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat for (EntrySummary e : entryList) { Set references = findReferences(e.getEntry()); for (String ref : references) { - Element tgt = resolveInBundle(entries, ref, e.getEntry().getChildValue("fullUrl"), e.getResource().fhirType(), e.getResource().getIdBase()); + Element tgt = resolveInBundle(entries, ref, e.getEntry().getChildValue(FULL_URL), e.getResource().fhirType(), e.getResource().getIdBase()); if (tgt != null) { EntrySummary t = entryForTarget(entryList, tgt); if (t != null) { @@ -4364,9 +4400,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat for (EntrySummary e : entryList) { Element entry = e.getEntry(); if (isError) { - rule(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry" + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue("fullUrl") != null ? "'" + entry.getChildValue("fullUrl") + "'" : "")); + rule(errors, 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) + "'" : "")); } else { - warning(errors, IssueType.INFORMATIONAL, entry.line(), entry.col(), stack.addToLiteralPath("entry" + '[' + (i + 1) + ']'), visited.contains(e), I18nConstants.BUNDLE_BUNDLE_ENTRY_ORPHAN, (entry.getChildValue("fullUrl") != null ? "'" + entry.getChildValue("fullUrl") + "'" : "")); + warning(errors, 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) + "'" : "")); } i++; } @@ -4395,17 +4431,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private void followResourceLinks(Element entry, Map visitedResources, Map candidateEntries, List candidateResources, List errors, NodeStack stack, int depth) { - Element resource = entry.getNamedChild("resource"); + Element resource = entry.getNamedChild(RESOURCE); if (visitedResources.containsValue(resource)) return; - visitedResources.put(entry.getNamedChildValue("fullUrl"), resource); + visitedResources.put(entry.getNamedChildValue(FULL_URL), resource); String type = null; 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 - IndexedElement r = getFromBundle(stack.getElement(), reference, entry.getChildValue("fullUrl"), new ArrayList(), stack.addToLiteralPath("entry[" + candidateResources.indexOf(resource) + "]"), type, "transaction".equals(stack.getElement().getChildValue("type"))); + IndexedElement r = getFromBundle(stack.getElement(), reference, entry.getChildValue(FULL_URL), new ArrayList(), stack.addToLiteralPath("entry[" + candidateResources.indexOf(resource) + "]"), type, "transaction".equals(stack.getElement().getChildValue(TYPE))); if (r != null && !visitedResources.containsValue(r.getMatch())) { followResourceLinks(candidateEntries.get(r.getMatch()), visitedResources, candidateEntries, candidateResources, errors, stack, depth + 1); } @@ -4981,8 +5017,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (ed.hasSlicing()) { if (slicer != null && slicer.getPath().equals(ed.getPath())) { String errorContext = "profile " + profile.getUrl(); - if (!resource.getChildValue("id").isEmpty()) - errorContext += "; instance " + resource.getChildValue("id"); + if (!resource.getChildValue(ID).isEmpty()) + errorContext += "; instance " + resource.getChildValue(ID); throw new DefinitionException(formatMessage(I18nConstants.SLICE_ENCOUNTERED_MIDWAY_THROUGH_SET_PATH___ID___, slicer.getPath(), slicer.getId(), errorContext)); } slicer = ed; @@ -5114,7 +5150,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (isBundleEntry(ei.getPath())) { Element req = ep.getNamedChild("request"); Element resp = ep.getNamedChild("response"); - Element fullUrl = ep.getNamedChild("fullUrl"); + Element fullUrl = ep.getNamedChild(FULL_URL); Element method = null; Element url = null; if (req != null) { @@ -5268,7 +5304,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String type = defn.getKind() == StructureDefinitionKind.LOGICAL ? defn.getId() : defn.getType(); // special case: we have a bundle, and the profile is not for a bundle. We'll try the first entry instead - if (!type.equals(resourceName) && resourceName.equals("Bundle")) { + if (!type.equals(resourceName) && resourceName.equals(BUNDLE)) { NodeStack first = getFirstEntry(stack); if (first != null && first.getElement().getType().equals(type)) { element = first.element; @@ -5281,9 +5317,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ok = rule(errors, IssueType.INVALID, -1, -1, stack.getLiteralPath(), type.equals(resourceName), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, type, resourceName); if (ok) { - if (idstatus == IdStatus.REQUIRED && (element.getNamedChild("id") == null)) + if (idstatus == IdStatus.REQUIRED && (element.getNamedChild(ID) == null)) rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_MISSING); - else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild("id") != null)) + else if (idstatus == IdStatus.PROHIBITED && (element.getNamedChild(ID) != null)) rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.RESOURCE_RES_ID_PROHIBITED); start(hostContext, errors, element, element, defn, stack); // root is both definition and type } @@ -5291,10 +5327,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private NodeStack getFirstEntry(NodeStack bundle) { List list = new ArrayList(); - bundle.getElement().getNamedChildren("entry", list); + bundle.getElement().getNamedChildren(ENTRY, list); if (list.isEmpty()) return null; - Element resource = list.get(0).getNamedChild("resource"); + Element resource = list.get(0).getNamedChild(RESOURCE); if (resource == null) return null; else { @@ -5315,7 +5351,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat validateDocumentReference(errors, entries, section, stack, fullUrl, id, false, "focus", "Section"); List sectionEntries = new ArrayList(); - section.getNamedChildren("entry", sectionEntries); + section.getNamedChildren(ENTRY, sectionEntries); int j = 1; for (Element sectionEntry : sectionEntries) { NodeStack localStack2 = localStack.push(sectionEntry, j, null, null); 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 b89b5f5d5..efc8fce7e 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 @@ -66,7 +66,8 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour List objects = new ArrayList(examples.size()); for (String id : names) { - objects.add(new Object[] { id, examples.get(id)}); + //if (id.equals("bundle-documentation-bad.json")) + objects.add(new Object[] { id, examples.get(id)}); } return objects; } diff --git a/pom.xml b/pom.xml index 45fc01c59..e054180db 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 4.2.0 - 1.0.46-SNAPSHOT + 1.0.48-SNAPSHOT org.hl7.fhir.core