diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9622adf3d..76df95f1f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,6 @@ -Other changes: -* Fix trailing slashes for JAVA_HOME tests +Validator: +* fix bug in client caching for unidentified value sets +* look for codesystem definitions in local context +* fix path error in Questionnaire Response validation +* fix up r4b list of canonical resource types +* better version specific resolution of special canonical resources \ No newline at end of file diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 7f0e5b37f..7277bef27 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index a67967b5e..306b161f2 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index b375c8ed4..a696de0b8 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 5177f99c7..37ed55327 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index 72cee3441..d52d47b9f 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 42cce980e..b29cf71fb 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index f504f22d8..188176752 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -691,8 +691,8 @@ public class ProfileUtilities extends TranslatingUtilities { } if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()); + ce++; if (e.hasId()) { - ce++; String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)"; messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); } @@ -2470,7 +2470,7 @@ public class ProfileUtilities extends TranslatingUtilities { int i = 0; while (i < markdown.length()) { if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { - int j = i + 2; + int j = i + 2; while (j < markdown.length() && markdown.charAt(j) != ')') j++; if (j < markdown.length()) { @@ -2492,7 +2492,8 @@ public class ProfileUtilities extends TranslatingUtilities { i = i + 1; } else { b.append("]("); - b.append(webUrl); + // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? + // b.append(webUrl); i = i + 1; } } else @@ -4773,7 +4774,7 @@ public class ProfileUtilities extends TranslatingUtilities { private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) { String ref = pkp.getLinkFor(corePath, value.fhirType()); - if (ref != null) { + if (ref != null && ref.contains(".html")) { ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#"; } else { ref = "?gen-fv?"; 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 ae301a6a4..a7378609c 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 @@ -53,6 +53,7 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.NoTerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.r5.conformance.ProfileUtilities; +import org.hl7.fhir.r5.context.BaseWorkerContext.ResourceProxy; import org.hl7.fhir.r5.context.CanonicalResourceManager.CanonicalResourceProxy; import org.hl7.fhir.r5.context.IWorkerContext.PackageVersion; import org.hl7.fhir.r5.context.IWorkerContext.ILoggingService.LogCategory; @@ -111,6 +112,7 @@ import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorCla import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r5.terminologies.ValueSetExpanderSimple; import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; import org.hl7.fhir.utilities.OIDUtils; import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.ToolingClientLogger; @@ -130,6 +132,35 @@ import ca.uhn.fhir.model.valueset.BundleEntrySearchModeEnum; public abstract class BaseWorkerContext extends I18nBase implements IWorkerContext{ + public class ResourceProxy { + private Resource resource; + private CanonicalResourceProxy proxy; + + public ResourceProxy(Resource resource) { + super(); + this.resource = resource; + } + public ResourceProxy(CanonicalResourceProxy proxy) { + super(); + this.proxy = proxy; + } + + public Resource getResource() { + return resource != null ? resource : proxy.getResource(); + } + + public String getUrl() { + if (resource == null) { + return proxy.getUrl(); + } else if (resource instanceof CanonicalResource) { + return ((CanonicalResource) resource).getUrl(); + } else { + return null; + } + } + + } + public class MetadataResourceVersionComparator implements Comparator { private List list; @@ -166,7 +197,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte private boolean isTxCaching; private Set cached = new HashSet<>(); - private Map> allResourcesById = new HashMap>(); + private Map> allResourcesById = new HashMap>(); // all maps are to the full URI private CanonicalResourceManager codeSystems = new CanonicalResourceManager(false); private Set supportedCodeSystems = new HashSet(); @@ -279,6 +310,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte public void registerResourceFromPackage(CanonicalResourceProxy r, PackageVersion packageInfo) throws FHIRException { synchronized (lock) { + Map map = allResourcesById.get(r.getType()); + if (map == null) { + map = new HashMap(); + allResourcesById.put(r.getType(), map); + } + map.put(r.getId(), new ResourceProxy(r)); String url = r.getUrl(); if (!allowLoadingDuplicates && hasResource(r.getType(), url)) { @@ -341,12 +378,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte public void cacheResourceFromPackage(Resource r, PackageVersion packageInfo) throws FHIRException { synchronized (lock) { - Map map = allResourcesById.get(r.fhirType()); + Map map = allResourcesById.get(r.fhirType()); if (map == null) { - map = new HashMap(); + map = new HashMap(); allResourcesById.put(r.fhirType(), map); } - map.put(r.getId(), r); + if ((packageInfo == null || !packageInfo.isExamplesPackage()) || !map.containsKey(r.getId())) { + map.put(r.getId(), new ResourceProxy(r)); + } if (r instanceof CodeSystem || r instanceof NamingSystem) { oidCache.clear(); @@ -892,6 +931,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte @Override public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs) { + ValidationContextCarrier ctxt = new ValidationContextCarrier(); + return validateCode(options, code, vs, ctxt); + } + + @Override + public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt) { if (options == null) { options = ValidationOptions.defaults(); } @@ -912,7 +957,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (options.isUseClient()) { // ok, first we try to validate locally try { - ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this); + ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, this, ctxt); if (!vsc.isServerSide(code.getSystem())) { res = vsc.validateCode(code); if (txCache != null) { @@ -1033,13 +1078,15 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } if (vs != null) { - if (isTxCaching && cacheId != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) { + if (isTxCaching && cacheId != null && vs.getUrl() != null && cached.contains(vs.getUrl()+"|"+vs.getVersion())) { pin.addParameter().setName("url").setValue(new UriType(vs.getUrl()+(vs.hasVersion() ? "|"+vs.getVersion() : ""))); } else if (options.getVsAsUrl()){ pin.addParameter().setName("url").setValue(new StringType(vs.getUrl())); } else { pin.addParameter().setName("valueSet").setResource(vs); - cached.add(vs.getUrl()+"|"+vs.getVersion()); + if (vs.getUrl() != null) { + cached.add(vs.getUrl()+"|"+vs.getVersion()); + } } cache = true; addDependentResources(pin, vs); @@ -1246,8 +1293,12 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte return fetchResourceWithException(cls, uri, null); } - @SuppressWarnings("unchecked") public T fetchResourceWithException(Class class_, String uri, CanonicalResource source) throws FHIRException { + return fetchResourceWithException(class_, uri, null, source); + } + + @SuppressWarnings("unchecked") + public T fetchResourceWithException(Class class_, String uri, String version, CanonicalResource source) throws FHIRException { if (uri == null) { return null; } @@ -1257,7 +1308,6 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } synchronized (lock) { - String version = null; if (uri.contains("|")) { version = uri.substring(uri.lastIndexOf("|")+1); uri = uri.substring(0, uri.lastIndexOf("|")); @@ -1305,13 +1355,23 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (questionnaires.has(uri)) { return (T) questionnaires.get(uri, version); } - for (Map rt : allResourcesById.values()) { - for (Resource r : rt.values()) { - if (r instanceof CanonicalResource) { - CanonicalResource mr = (CanonicalResource) r; - if (uri.equals(mr.getUrl())) { - return (T) mr; - } + if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) { + return null; + } + + // it might be a special URL. + if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { + Resource res = null; // findTxValueSet(uri); + if (res != null) { + return (T) res; + } + } + for (Map rt : allResourcesById.values()) { + for (ResourceProxy r : rt.values()) { + if (uri.equals(r.getUrl())) { + if (version == null || version == r.getResource().getMeta().getVersionId()) { + return (T) r.getResource(); + } } } } @@ -1355,20 +1415,6 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (class_ == Questionnaire.class) { return (T) questionnaires.get(uri, version); } - if (class_ == null) { - if (uri.matches(Constants.URI_REGEX) && !uri.contains("ValueSet")) { - return null; - } - - // it might be a special URL. - if (Utilities.isAbsoluteUrl(uri) || uri.startsWith("ValueSet/")) { - Resource res = null; // findTxValueSet(uri); - if (res != null) { - return (T) res; - } - } - return null; - } if (supportedCodeSystems.contains(uri)) { return null; } @@ -1494,13 +1540,10 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte if (questionnaires.has(uri)) { return (T) questionnaires.get(uri, version); } - for (Map rt : allResourcesById.values()) { - for (Resource r : rt.values()) { - if (r instanceof CanonicalResource) { - CanonicalResource mr = (CanonicalResource) r; - if (uri.equals(mr.getUrl())) { - return (T) mr; - } + for (Map rt : allResourcesById.values()) { + for (ResourceProxy r : rt.values()) { + if (uri.equals(r.getUrl())) { + return (T) r.getResource(); } } } @@ -1574,7 +1617,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte String[] parts = uri.split("\\/"); if (!Utilities.noString(type) && parts.length == 1) { if (allResourcesById.containsKey(type)) { - return allResourcesById.get(type).get(parts[0]); + return allResourcesById.get(type).get(parts[0]).getResource(); } else { return null; } @@ -1585,7 +1628,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte throw new Error(formatMessage(I18nConstants.RESOURCE_TYPE_MISMATCH_FOR___, type, uri)); } } - return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]); + return allResourcesById.get(parts[parts.length-2]).get(parts[parts.length-1]).getResource(); } else { throw new Error(formatMessage(I18nConstants.UNABLE_TO_PROCESS_REQUEST_FOR_RESOURCE_FOR___, type, uri)); } @@ -1608,6 +1651,14 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte } } + public T fetchResource(Class class_, String uri, String version) { + try { + return fetchResourceWithException(class_, uri, version, null); + } catch (FHIRException e) { + throw new Error(e); + } + } + @Override public boolean hasResource(Class class_, String uri) { try { @@ -1697,13 +1748,13 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte public void dropResource(String fhirType, String id) { synchronized (lock) { - Map map = allResourcesById.get(fhirType); + Map map = allResourcesById.get(fhirType); if (map == null) { - map = new HashMap(); + map = new HashMap(); allResourcesById.put(fhirType, map); } if (map.containsKey(id)) { - map.remove(id); + map.remove(id); // this is a challenge because we might have more than one resource with this id (different versions) } if (fhirType.equals("StructureDefinition")) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java index 73b60a05e..34bfc6099 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/CanonicalResourceManager.java @@ -236,6 +236,9 @@ public class CanonicalResourceManager { } } CachedCanonicalResource existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0"); + if (existing != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) { + return; + } if (existing != null) { list.remove(existing); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 7f90e56c1..ed0afccfc 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -45,6 +45,7 @@ import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.r5.context.TerminologyCache.CacheToken; +import org.hl7.fhir.r5.elementmodel.Element; import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.formats.ParserType; import org.hl7.fhir.r5.model.Bundle; @@ -64,11 +65,13 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r5.utils.validation.IResourceValidator; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.TranslationServices; import org.hl7.fhir.utilities.npm.BasePackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationOptions; import com.google.gson.JsonSyntaxException; @@ -167,6 +170,9 @@ public interface IWorkerContext { public String getVersion() { return version; } + public boolean isExamplesPackage() { + return !(id.startsWith("hl7.fhir.") && id.endsWith(".example")); + } } public class PackageDetails extends PackageVersion { @@ -190,13 +196,14 @@ public interface IWorkerContext { } } + public interface ICanonicalResourceLocator { void findResource(Object caller, String url); // if it can be found, put it in the context } public interface IContextResourceLoader { /** - * @return List of the resource types that shoud be loaded + * @return List of the resource types that should be loaded */ String[] getTypes(); @@ -241,7 +248,6 @@ public interface IWorkerContext { IContextResourceLoader getNewLoader(NpmPackage npm) throws JsonSyntaxException, IOException; } - /** * Get the versions of the definitions loaded in context * @return @@ -334,6 +340,7 @@ public interface IWorkerContext { */ public T fetchResource(Class class_, String uri); public T fetchResourceWithException(Class class_, String uri) throws FHIRException; + public T fetchResource(Class class_, String uri, String version); /** has the same functionality as fetchResource, but passes in information about the source of the * reference (this may affect resolution of version) @@ -741,6 +748,8 @@ public interface IWorkerContext { * @return */ public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs); + + public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt); public void validateCodeBatch(ValidationOptions options, List codes, ValueSet vs); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java index 139206271..8f0d1a1c1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ListRenderer.java @@ -59,7 +59,8 @@ public class ListRenderer extends ResourceRenderer { shortForRef(td, list.get("subject")); } if (list.has("encounter")) { - shortForRef(td.tx("Encounter: "), list.get("encounter")); + td.tx("Encounter: "); + shortForRef(td, list.get("encounter")); } if (list.has("source")) { td.tx("Source: "); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index b2f6739c3..df2000946 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -703,7 +703,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer { } if (i.hasAnswerValueSet()) { XhtmlNode ans = item(ul, "Answers"); - if (Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { + if (!Utilities.noString(i.getAnswerValueSet()) && i.getAnswerValueSet().startsWith("#")) { ValueSet vs = (ValueSet) q.getContained(i.getAnswerValueSet().substring(1)); if (vs == null) { ans.tx(i.getAnswerValueSet()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index 6334320a5..65f59608d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -202,7 +202,7 @@ public abstract class ResourceRenderer extends DataRenderer { } // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative String display = r.hasDisplayElement() ? r.getDisplay() : null; - String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; + String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; if (display == null && (tr == null || tr.getResource() == null)) { c.addText(r.getReference()); @@ -214,6 +214,11 @@ public abstract class ResourceRenderer extends DataRenderer { if ((tr == null || !tr.getReference().startsWith("#")) && name != null) { x.addText(" \""+name+"\""); } + if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID)) { + x.addText("(#"+r.getExtensionString(ToolingExtensions.EXT_TARGET_ID)+")"); + } else if (r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { + x.addText("(#/"+r.getExtensionString(ToolingExtensions.EXT_TARGET_PATH)+")"); + } } else { if (display != null) { c.addText(display); @@ -272,6 +277,11 @@ public abstract class ResourceRenderer extends DataRenderer { } return null; } + String version = null; + if (url.contains("/_history/")) { + version = url.substring(url.indexOf("/_history/")+10); + url = url.substring(0, url.indexOf("/_history/")); + } if (rcontext != null) { BundleEntryComponent bundleResource = rcontext.resolve(url); @@ -279,7 +289,7 @@ public abstract class ResourceRenderer extends DataRenderer { String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + bundleResource.getResource().getId(); return new ResourceWithReference(bundleUrl, new ResourceWrapperDirect(this.context, bundleResource.getResource())); } - org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url); + org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url, version); if (bundleElement != null) { String bundleUrl = null; Element br = bundleElement.getNamedChild("resource"); @@ -292,7 +302,7 @@ public abstract class ResourceRenderer extends DataRenderer { } } - Resource ae = getContext().getWorker().fetchResource(null, url); + Resource ae = getContext().getWorker().fetchResource(null, url, version); if (ae != null) return new ResourceWithReference(url, new ResourceWrapperDirect(this.context, ae)); else if (context.getResolver() != null) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java index 84f0a748d..13cbb5977 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java @@ -101,7 +101,7 @@ public class Resolver { return null; } - public org.hl7.fhir.r5.elementmodel.Element resolveElement(String value) { + public org.hl7.fhir.r5.elementmodel.Element resolveElement(String value, String version) { if (value.startsWith("#")) { if (resourceElement != null) { for (org.hl7.fhir.r5.elementmodel.Element r : resourceElement.getChildrenByName("contained")) { @@ -115,10 +115,18 @@ public class Resolver { if (containerElement != null) { for (org.hl7.fhir.r5.elementmodel.Element be : containerElement.getChildren("entry")) { org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource"); - if (value.equals(be.getChildValue("fullUrl"))) - return be; - if (value.equals(res.fhirType()+"/"+res.getChildValue("id"))) - return be; + if (res != null) { + if (value.equals(be.getChildValue("fullUrl"))) { + if (checkVersion(version, res)) { + return be; + } + } + if (value.equals(res.fhirType()+"/"+res.getChildValue("id"))) { + if (checkVersion(version, res)) { + return be; + } + } + } } } } @@ -126,13 +134,27 @@ public class Resolver { if (containerElement != null) { for (org.hl7.fhir.r5.elementmodel.Element p : containerElement.getChildren("parameter")) { org.hl7.fhir.r5.elementmodel.Element res = p.getNamedChild("resource"); - if (res != null && value.equals(res.fhirType()+"/"+res.getChildValue("id"))) - return p; + if (res != null && value.equals(res.fhirType()+"/"+res.getChildValue("id"))) { + if (checkVersion(version, res)) { + return p; + } + } } } } return null; } + + private boolean checkVersion(String version, org.hl7.fhir.r5.elementmodel.Element res) { + if (version == null) { + return true; + } else if (!res.hasChild("meta")) { + return false; + } else { + org.hl7.fhir.r5.elementmodel.Element meta = res.getNamedChild("meta"); + return version.equals(meta.getChildValue("version")); + } + } } public static class ResourceWithReference { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index 1e0667674..93d72845e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -56,6 +56,9 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -69,12 +72,52 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe private IWorkerContext context; private Map inner = new HashMap<>(); private ValidationOptions options; + private ValidationContextCarrier localContext; + private List localSystems = new ArrayList<>(); public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context) { this.valueset = source; this.context = context; this.options = options; } + + public ValueSetCheckerSimple(ValidationOptions options, ValueSet source, IWorkerContext context, ValidationContextCarrier ctxt) { + this.valueset = source; + this.context = context; + this.options = options; + this.localContext = ctxt; + analyseValueSet(); + } + + private void analyseValueSet() { + if (localContext != null) { + if (valueset != null) { + for (ConceptSetComponent i : valueset.getCompose().getInclude()) { + analyseComponent(i); + } + for (ConceptSetComponent i : valueset.getCompose().getExclude()) { + analyseComponent(i); + } + } + } + } + + private void analyseComponent(ConceptSetComponent i) { + if (i.getSystemElement().hasExtension(ToolingExtensions.EXT_VALUESET_SYSTEM)) { + String ref = i.getSystemElement().getExtensionString(ToolingExtensions.EXT_VALUESET_SYSTEM); + if (ref.startsWith("#")) { + String id = ref.substring(1); + for (ValidationContextResourceProxy t : localContext.getResources()) { + CodeSystem cs = (CodeSystem) t.loadContainedResource(id, CodeSystem.class); + if (cs != null) { + localSystems.add(cs); + } + } + } else { + throw new Error("Not done yet #2: "+ref); + } + } + } public ValidationResult validateCode(CodeableConcept code) throws FHIRException { // first, we validate the codings themselves @@ -85,7 +128,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (!c.hasSystem()) { warnings.add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); } - CodeSystem cs = context.fetchCodeSystem(c.getSystem()); + CodeSystem cs = resolveCodeSystem(c.getSystem()); ValidationResult res = null; if (cs == null || cs.getContent() != CodeSystemContentMode.COMPLETE) { res = context.validateCode(options.noClient(), c, null); @@ -124,6 +167,19 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } } + public CodeSystem resolveCodeSystem(String system) { + for (CodeSystem t : localSystems) { + if (t.getUrl().equals(system)) { + return t; + } + } + CodeSystem cs = context.fetchCodeSystem(system); + if (cs == null) { + cs = findSpecialCodeSystem(system); + } + return cs; + } + public ValidationResult validateCode(Coding code) throws FHIRException { String warningMessage = null; // first, we validate the concept itself @@ -144,11 +200,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } inExpansion = checkExpansion(code); inInclude = checkInclude(code); - CodeSystem cs = context.fetchCodeSystem(system); - if (cs == null) { - cs = findSpecialCodeSystem(system); - } - System.out.print(""); + CodeSystem cs = resolveCodeSystem(system); if (cs == null) { warningMessage = "Unable to resolve system "+system; if (!inExpansion) { @@ -499,7 +551,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (vsi.hasFilter()) { return null; } - CodeSystem cs = context.fetchCodeSystem(vsi.getSystem()); + CodeSystem cs = resolveCodeSystem(vsi.getSystem()); if (cs == null) { return null; } @@ -605,7 +657,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (!system.equals(vsi.getSystem())) return false; // ok, we need the code system - CodeSystem cs = context.fetchCodeSystem(system); + CodeSystem cs = resolveCodeSystem(system); if (cs == null || (cs.getContent() != CodeSystemContentMode.COMPLETE && cs.getContent() != CodeSystemContentMode.FRAGMENT)) { // make up a transient value set with ValueSet vs = new ValueSet(); @@ -710,7 +762,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe return inner.get(url); } ValueSet vs = context.fetchResource(ValueSet.class, url); - ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, context); + ValueSetCheckerSimple vsc = new ValueSetCheckerSimple(options, vs, context, localContext); inner.put(url, vsc); return vsc; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 6872e5e63..7ca4ba646 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -5539,7 +5539,7 @@ public class FHIRPathEngine { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_NAME, expr.getName()); } } else if (expr.getKind() == Kind.Group) { - throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP); + throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP, expr.toString()); } else if (expr.getKind() == Kind.Constant) { throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST); } 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 2c40dff4d..ef912788e 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 @@ -195,6 +195,9 @@ public class ToolingExtensions { public static final String EXT_XML_NAME = "http://hl7.org/fhir/StructureDefinition/elementdefinition-xml-name"; public static final String EXT_BINDING_STYLE = "http://hl7.org/fhir/StructureDefinition/elementdefinition-binding-style"; public static final String EXT_BINARY_FORMAT = "http://hl7.org/fhir/StructureDefinition/implementationguide-resource-format"; + public static final String EXT_TARGET_ID = "http://hl7.org/fhir/StructureDefinition/targetElement"; + public static final String EXT_TARGET_PATH = "http://hl7.org/fhir/StructureDefinition/targetPath"; + public static final String EXT_VALUESET_SYSTEM = "http://hl7.org/fhir/StructureDefinition/valueset-system"; // specific extension helpers diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/ValidationContextCarrier.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/ValidationContextCarrier.java new file mode 100644 index 000000000..313919812 --- /dev/null +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/validation/ValidationContextCarrier.java @@ -0,0 +1,81 @@ +package org.hl7.fhir.r5.utils.validation; + +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.elementmodel.Element; +import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Questionnaire; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.utilities.validation.ValidationMessage; + +public class ValidationContextCarrier { + /** + * + * When the validator is calling validateCode, it typically has a partially loaded resource that may provide + * additional resources that are relevant to the validation. This is a handle back into the validator context + * to ask for the resource to be fully loaded if it becomes relevant. Note that the resource may fail to load + * (e.g. if it's part of what's being validated) and if it does, the validator will record the validation + * issues before throwing an error + * + * This is a reference back int + * + */ + public interface IValidationContextResourceLoader { + public Resource loadContainedResource(List errors, String path, Element resource, String id, Class class1) throws FHIRException; + } + + /** + * A list of resources that provide context - typically, a container resource, and a bundle resource. + * iterate these in order looking for contained resources + * + */ + public static class ValidationContextResourceProxy { + + // either a resource + private Resource resource; + + + // or an element and a loader + private Element element; + private IValidationContextResourceLoader loader; + private List errors; + private String path; + + public ValidationContextResourceProxy(Resource resource) { + this.resource = resource; + } + + public ValidationContextResourceProxy(List errors, String path, Element element, IValidationContextResourceLoader loader) { + this.errors = errors; + this.path = path; + this.element = element; + this.loader = loader; + } + + public Resource loadContainedResource(String id, Class class1) throws FHIRException { + if (resource == null) { + Resource res = loader.loadContainedResource(errors, path, element, id, class1); + return res; + } else { + if (resource instanceof DomainResource) { + for (Resource r : ((DomainResource) resource).getContained()) { + if (r.getId().equals(id)) { + if (class1.isInstance(r)) + return r; + } + } + } + return null; + } + } + } + + private List resources = new ArrayList<>(); + + public List getResources() { + return resources; + } + +} diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index 1950d77f4..5035c39a6 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index 57d4f00a0..20e06f6c1 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SIDUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SIDUtilities.java index 69664a879..7f7d11101 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SIDUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SIDUtilities.java @@ -10,11 +10,9 @@ public class SIDUtilities { public static List codeSystemList() { List codeSystems = new ArrayList<>(); codeSystems.add("http://hl7.org/fhir/sid/ndc"); - codeSystems.add("http://hl7.org/fhir/sid/icd-10"); codeSystems.add("http://hl7.org/fhir/sid/icpc2"); codeSystems.add("http://hl7.org/fhir/sid/icd-9"); codeSystems.add("http://hl7.org/fhir/sid/icd-10"); - codeSystems.add("http://hl7.org/fhir/sid/icpc2"); codeSystems.add("http://hl7.org/fhir/sid/cvx"); codeSystems.add("http://hl7.org/fhir/sid/srt"); codeSystems.add("http://hl7.org/fhir/sid/icd-10-vn"); @@ -55,8 +53,38 @@ public class SIDUtilities { allSystems.addAll(idSystemList()); return allSystems; } - - - + public static boolean isInvalidVersion(String u, String v) { + if (v == null) { + return false; + } else { + if (idSystemList().contains(u)) { + return true; + } else { + switch (u) { + case "http://hl7.org/fhir/sid/ndc": + return v.matches("[\\d]{8}"); + case "http://hl7.org/fhir/sid/icpc2": + return false; + case "http://hl7.org/fhir/sid/icd-10": + return false; + case "http://hl7.org/fhir/sid/icd-9": + return false; + case "http://hl7.org/fhir/sid/cvx": + return v.matches("[\\d]{8}"); + case "http://hl7.org/fhir/sid/srt": + return false; + case "http://hl7.org/fhir/sid/icd-10-vn": + return false; + case "http://hl7.org/fhir/sid/icd-10-cm": + return false; + case "http://hl7.org/fhir/sid/icd-9-cm": + return false; + default: + return true; + } + } + } + } + } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index 18640920f..6582909c3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -397,7 +397,6 @@ public class VersionUtilities { } if (isR4Ver(version)) { - res.add("CodeSystem"); res.add("ActivityDefinition"); res.add("CapabilityStatement"); @@ -429,6 +428,38 @@ public class VersionUtilities { res.add("TestScript"); res.add("ValueSet"); } + if (isR4BVer(version)) { + res.add("ActivityDefinition"); + res.add("CapabilityStatement"); + res.add("ChargeItemDefinition"); + res.add("Citation"); + res.add("CodeSystem"); + res.add("CompartmentDefinition"); + res.add("ConceptMap"); + res.add("EventDefinition"); + res.add("Evidence"); + res.add("EvidenceReport"); + res.add("EvidenceVariable"); + res.add("ExampleScenario"); + res.add("GraphDefinition"); + res.add("ImplementationGuide"); + res.add("Library"); + res.add("Measure"); + res.add("MessageDefinition"); + res.add("NamingSystem"); + res.add("OperationDefinition"); + res.add("PlanDefinition"); + res.add("Questionnaire"); + res.add("ResearchDefinition"); + res.add("ResearchElementDefinition"); + res.add("SearchParameter"); + res.add("StructureDefinition"); + res.add("StructureMap"); + res.add("SubscriptionTopic"); + res.add("TerminologyCapabilities"); + res.add("TestScript"); + res.add("ValueSet"); + } if (isR5Ver(version) || "current".equals(version)) { @@ -479,5 +510,16 @@ public class VersionUtilities { return mm1 != null && mm2 != null && mm1.equals(mm2); } + public static boolean isR5VerOrLater(String version) { + if (version == null) { + return false; + } + if (version.startsWith(CURRENT_VERSION) || version.equals("current")) { + return true; + } + String v = getMajMin(version); + return v.compareTo("4.5") >= 0; + } + } \ No newline at end of file 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 0e0a141dc..1a67ff30d 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 @@ -1,5 +1,6 @@ package org.hl7.fhir.utilities.i18n; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; public class I18nConstants { @@ -483,6 +484,8 @@ public class I18nConstants { public static final String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = "TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS"; public static final String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE = "Type_Specific_Checks_DT_Decimal_Range"; public static final String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID = "Type_Specific_Checks_DT_Decimal_Valid"; + public static final String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT = "Type_Specific_Checks_DT_Decimal_GT"; + public static final String TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT = "Type_Specific_Checks_DT_Decimal_LT"; public static final String TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE = "TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_IETF_SYSTEM_VALUE"; public static final String TYPE_SPECIFIC_CHECKS_DT_IDENTIFIER_SYSTEM = "Type_Specific_Checks_DT_Identifier_System"; public static final String TYPE_SPECIFIC_CHECKS_DT_ID_VALID = "Type_Specific_Checks_DT_ID_Valid"; @@ -511,6 +514,7 @@ public class I18nConstants { public static final String TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE = "TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE"; public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE"; public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC"; public static final String TYPE_SPECIFIC_CHECKS_DT_UUID_STRAT = "Type_Specific_Checks_DT_UUID_Strat"; public static final String TYPE_SPECIFIC_CHECKS_DT_UUID_VALID = "Type_Specific_Checks_DT_UUID_Valid"; public static final String UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER = "Unable_to_connect_to_terminology_server"; @@ -659,6 +663,36 @@ public class I18nConstants { public static final String CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL = "CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL"; public static final String CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG = "CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG"; public static final String CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING = "CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG"; + public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM = "TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM"; + public static final String TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR = "TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR"; + public static final String TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = "TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING"; } + + diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 2536ee23e..bf567fc41 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -186,6 +186,8 @@ Type_Specific_Checks_DT_DateTime_Valid = Not a valid date/time ({0}) Type_Specific_Checks_DT_Date_Valid = Not a valid date ({0}) Type_Specific_Checks_DT_Decimal_Range = The value ''{0}'' is outside the range of commonly/reasonably supported decimals Type_Specific_Checks_DT_Decimal_Valid = The value ''{0}'' is not a valid decimal +Type_Specific_Checks_DT_Decimal_GT = value is greater than permitted maximum value of {0} +Type_Specific_Checks_DT_Decimal_LT = value is less than permitted minimum value of {0} Type_Specific_Checks_DT_ID_Valid = id value ''{0}'' is not valid Type_Specific_Checks_DT_Identifier_System = Identifier.system must be an absolute reference, not a local reference Type_Specific_Checks_DT_Instant_Valid = Not a valid instant ({0}) @@ -571,7 +573,7 @@ FHIRPATH_DISCRIMINATOR_TYPE_NONE = illegal use of ofType() in discriminator - no FHIRPATH_DISCRIMINATOR_TYPE_MULTIPLE = illegal use of ofType() in discriminator - Multiple possible types on {0} FHIRPATH_DISCRIMINATOR_NO_CODE = illegal use of ofType() in discriminator - Type has no code on {0} FHIRPATH_DISCRIMINATOR_BAD_NAME = illegal function name {0}() in discriminator -FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = illegal expression syntax in discriminator (group) +FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = illegal expression syntax in discriminator (group ''{0}'') FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = illegal expression syntax in discriminator (const) FHIRPATH_DISCRIMINATOR_CANT_FIND = Unable to resolve discriminator in definitions: {0} in profile {1} on element {2}, looking in profile {3} FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION = Unable to resolve discriminator {0} on {2} found in the definitions because the extension {1} wasn''t found in the profile {3} @@ -643,6 +645,7 @@ SD_ED_BIND_NO_BINDABLE = The element {0} has a binding, but no bindable types ar DISCRIMINATOR_BAD_PATH = Error processing path expression for discriminator: {0} (src = ''{1}'') SLICING_CANNOT_BE_EVALUATED = Slicing cannot be evaluated: {0} TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE = Canonical URL ''{0}'' does not resolve +TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC = Canonical URL ''{0}'' exists, but can't be loaded, so it can't be checked for validity TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE = Canonical URL ''{0}'' refers to a resource that has the wrong type. Found {1} expecting one of {2} CODESYSTEM_CS_NO_SUPPLEMENT = CodeSystem {0} is a supplement, so can't be used as a value in Coding.system CODESYSTEM_CS_SUPP_CANT_CHECK = CodeSystem {0} cannot be found, so can't check if concepts are valid @@ -670,4 +673,32 @@ CODESYSTEM_CS_NONHL7_MISSING_ELEMENT = CodeSystems SHOULD have a stated value fo CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL = CodeSystems SHOULD NOT have a stated value for the {0} element when they are a supplement CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_WRONG = CodeSystem Supplements SHALL have a content value of 'supplement' CODESYSTEM_CS_HL7_PRESENT_ELEMENT_SUPPL_MISSING = CodeSystem Supplements with a content value of 'supplement' SHALL have a supplements elemnet that specifies which code system is being supplemented +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY = Found {0} of type {2} in the profile validating a Quantity (so it must be a Quantity) +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH = The minValue in the profile has a system of {0} which is different to the system in the value {1} so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH = The minValue in the profile has a system code of {0} which is different to the system code in the value {1} so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE = The minValue in the profile doesn't have an actual value, so the minimum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE = The quantity doesn't have an actual value, so the minimum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM = The minValue in the profile has no system so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM = The value has no system so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE = The minValue in the profile has no code so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE = The value has no code so the minimum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC = There is no UCUM service, and the UCUM codes aren't identical, so the minimum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT = Unable to convert value {0} from unit {1} to minValue unit {2} based on UCUM definitions; minimum value is not valid +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG = The value in the instance ({2}) is less than the specified minimum value ({3}) +TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM = The value in the instance ({0} {1}) is less than the specified minValue ({2} {3}) after UCUM conversion +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY = Found {0} of type {2} in the profile validating a Quantity (so it must be a Quantity) +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH = The maxValue in the profile has a system of {0} which is different to the system in the value {1} so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH = The maxValue in the profile has a system code of {0} which is different to the system code in the value {1} so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE = The maxValue in the profile doesn't have an actual value, so the maximum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE = The quantity doesn't have an actual value, so the maximum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM = The maxValue in the profile has no system so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM = The value has no system so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE = The maxValue in the profile has no code so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE = The value has no code so the maximum value cannot be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC = There is no UCUM service, and the UCUM codes aren't identical, so the maximum value can't be checked +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT = Unable to convert value {0} from unit {1} to maxValue unit {2} based on UCUM definitions; maximum value is not valid +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG = The value in the instance ({2}) is greater than the specified maximum value ({3}) +TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM = The value in the instance ({0} {1}) is greater than the specified maxValue ({2} {3}) after UCUM conversion +TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR = Base64 encoded values are not allowed to contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway +TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING = Base64 encoded values SHOULD not contain any whitespace (per RFC 4648). Note that non-validating readers are encouraged to accept whitespace anyway diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index f73bfeca3..55d7534e0 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index fe10e899f..74c3102a1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -44,6 +44,7 @@ import org.hl7.fhir.r5.model.*; import org.hl7.fhir.r5.terminologies.ValueSetUtilities; import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -61,7 +62,8 @@ import java.util.Map; import static org.apache.commons.lang3.StringUtils.isBlank; -public class BaseValidator { +public class BaseValidator implements IValidationContextResourceLoader { + public class TrackedLocationRelatedMessage { private Object location; private ValidationMessage vmsg; @@ -996,7 +998,8 @@ public class BaseValidator { } } - protected Resource loadContainedResource(List errors, String path, Element resource, String id, Class class1) throws FHIRException { + @Override + public Resource loadContainedResource(List errors, String path, Element resource, String id, Class class1) throws FHIRException { for (Element contained : resource.getChildren("contained")) { if (contained.getIdBase().equals(id)) { return loadFoundResource(errors, path, contained, class1); @@ -1005,7 +1008,7 @@ public class BaseValidator { return null; } - protected Resource loadFoundResource(List errors, String path, Element resource, Class class1) throws FHIRException { + protected Resource loadFoundResource(List errors, String path, Element resource, Class class1) throws FHIRException { try { FhirPublication v = FhirPublication.fromCode(context.getVersion()); ByteArrayOutputStream bs = new ByteArrayOutputStream(); 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 245a22782..c714663b1 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 @@ -3,6 +3,9 @@ package org.hl7.fhir.validation; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; + +import org.fhir.ucum.UcumEssenceService; +import org.fhir.ucum.UcumException; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; @@ -227,6 +230,15 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP context = SimpleWorkerContext.fromDefinitions(source, ValidatorUtils.loaderForVersion(version), new PackageVersion(src)); ValidatorUtils.grabNatives(getBinaries(), source, "http://hl7.org/fhir"); } + // ucum-essence.xml should be in the class path. if it's not, ask about how to sort this out + // on https://chat.fhir.org/#narrow/stream/179167-hapi + try { + ClassLoader classLoader = ValidationEngine.class.getClassLoader(); + InputStream ue = classLoader.getResourceAsStream("ucum-essence.xml"); + context.setUcumService(new UcumEssenceService(ue)); + } catch (Exception e) { + throw new FHIRException("Error loading UCUM from embedded ucum-essence.xml: "+e.getMessage(), e); + } initContext(tt); } 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 ed248afdd..a2b3820d9 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 @@ -37,6 +37,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Calendar; @@ -51,6 +52,7 @@ import java.util.UUID; import org.apache.commons.codec.binary.Base64InputStream; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; +import org.fhir.ucum.Decimal; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; @@ -2112,52 +2114,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.INVALID, e.line(), e.col(), path, Utilities.isAbsoluteUrl(url), node.isContained() ? I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_CONTAINED : I18nConstants.TYPE_SPECIFIC_CHECKS_CANONICAL_ABSOLUTE, url); } else { - // now, do we check the URI target? - if (fetcher != null && !type.equals("uuid")) { - boolean found; - try { - found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || - SpecialExtensions.isKnownExtension(url) || isXverUrl(url) || fetcher.resolveURL(this, hostContext, path, url, type); - } catch (IOException e1) { - found = false; - } - if (!found) { - if (type.equals("canonical")) { - ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); - if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) { - rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); - } else { - hint(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); - } - } else { - if (url.contains("hl7.org") || url.contains("fhir.org")) { - rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); - } else if (url.contains("example.org") || url.contains("acme.com")) { - rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url); - } else { - warning(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); - } - } - } else { - if (type.equals("canonical")) { - ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); - if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) { - try { - Resource r = fetcher.fetchCanonicalResource(this, url); - if (r == null) { - rule(errors, IssueType.INVALID, e.line(), e.col(), path, found, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); - } else if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, isCorrectCanonicalType(r, context), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE, url, r.fhirType(), listExpectedCanonicalTypes(context))) { - if (rp == ReferenceValidationPolicy.CHECK_VALID) { - // todo.... - } - } - } catch (Exception ex) { - // won't happen - } - } - } - } - } + validateReference(hostContext, errors, path, type, context, e, url); } } if (type.equals(ID) && !"Resource.id".equals(context.getBase().getPath())) { @@ -2214,6 +2171,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!ok) { String value = encoded.length() < 100 ? encoded : "(snip)"; rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID, value); + } else { + boolean wsok = !base64HasWhitespace(encoded); + if (VersionUtilities.isR5VerOrLater(this.context.getVersion())) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_ERROR); + } else { + warning(errors, IssueType.INVALID, e.line(), e.col(), path, wsok, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_BASE64_NO_WS_WARNING); + } } if (ok && context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxSize")) { int size = countBase64DecodedBytes(encoded); @@ -2248,8 +2212,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (type.equals("decimal")) { if (e.primitiveValue() != null) { DecimalStatus ds = Utilities.checkDecimal(e.primitiveValue(), true, false); - if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) + if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, ds == DecimalStatus.OK || ds == DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_VALID, e.primitiveValue())) { warning(errors, IssueType.VALUE, e.line(), e.col(), path, ds != DecimalStatus.RANGE, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_RANGE, e.primitiveValue()); + try { + Decimal v = new Decimal(e.getValue()); + rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxValueIntegerType() || + !context.getMaxValueIntegerType().hasValue() || checkDecimalMaxValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_GT, (context.hasMaxValueIntegerType() ? context.getMaxValueIntegerType() : "")); + rule(errors, IssueType.INVALID, e.line(), e.col(), path, !context.hasMinValueIntegerType() || + !context.getMinValueIntegerType().hasValue() || checkDecimalMinValue(v, context.getMaxValueDecimalType().getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_LT, (context.hasMinValueIntegerType() ? context.getMinValueIntegerType() : "")); + } catch (Exception ex) { + // should never happen? + } + } } if (context.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { int dp = e.primitiveValue().contains(".") ? e.primitiveValue().substring(e.primitiveValue().indexOf(".")+1).length() : 0; @@ -2304,6 +2278,67 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // for nothing to check } + public void validateReference(ValidatorHostContext hostContext, List errors, String path, String type, ElementDefinition context, Element e, String url) { + // now, do we check the URI target? + if (fetcher != null && !type.equals("uuid")) { + boolean found; + try { + found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || + SpecialExtensions.isKnownExtension(url) || isXverUrl(url); + if (!found) { + found = fetcher.resolveURL(this, hostContext, path, url, type); + } + } catch (IOException e1) { + found = false; + } + if (!found) { + if (type.equals("canonical")) { + ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); + if (rp == ReferenceValidationPolicy.CHECK_EXISTS || rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); + } else { + hint(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE, url); + } + } else { + if (url.contains("hl7.org") || url.contains("fhir.org")) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); + } else if (url.contains("example.org") || url.contains("acme.com")) { + rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE, url); + } else { + warning(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE, url); + } + } + } else { + if (type.equals("canonical")) { + ReferenceValidationPolicy rp = policyAdvisor == null ? ReferenceValidationPolicy.CHECK_VALID : policyAdvisor.policyForReference(this, hostContext, path, url); + if (rp == ReferenceValidationPolicy.CHECK_EXISTS_AND_TYPE || rp == ReferenceValidationPolicy.CHECK_TYPE_IF_EXISTS || rp == ReferenceValidationPolicy.CHECK_VALID) { + try { + Resource r = null; + if (url.startsWith("#")) { + r = loadContainedResource(errors, path, hostContext.getRootResource(), url.substring(1), Resource.class); + } + if (r == null) { + fetcher.fetchCanonicalResource(this, url); + } + if (r == null) { + r = this.context.fetchResource(Resource.class, url); + } + if (r == null) { + warning(errors, IssueType.INVALID, e.line(), e.col(), path, rp != ReferenceValidationPolicy.CHECK_VALID, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE_NC, url); + } else if (rule(errors, IssueType.INVALID, e.line(), e.col(), path, isCorrectCanonicalType(r, context), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE, url, r.fhirType(), listExpectedCanonicalTypes(context))) { + if (rp == ReferenceValidationPolicy.CHECK_VALID) { + // todo.... + } + } + } catch (Exception ex) { + // won't happen + } + } + } + } + } + } + private List listExpectedCanonicalTypes(ElementDefinition context) { List res = new ArrayList<>(); TypeRefComponent tr = context.getType("canonical"); @@ -2335,11 +2370,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean isCorrectCanonicalType(Resource r, CanonicalType p) { String url = p.getValue(); - if (url != null && url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { - url = url.substring("http://hl7.org/fhir/StructureDefinition/".length()); - return Utilities.existsInList(url, "Resource", "CanonicalResource") || url.equals(r.fhirType()); + String t = null; + if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) { + t = url.substring("http://hl7.org/fhir/StructureDefinition/".length()); + } else { + StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); + if (sd != null) { + t = sd.getType(); + } + } + if (t == null ) { + return false; + } else { + return Utilities.existsInList(t, "Resource", "CanonicalResource") || t.equals(r.fhirType()); } - return false; } private boolean isCanonicalURLElement(Element e) { @@ -2408,6 +2452,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return ok; } + private boolean base64HasWhitespace(String theEncoded) { + if (theEncoded == null) { + return false; + } + for (int i = 0; i < theEncoded.length(); i++) { + char nextChar = theEncoded.charAt(i); + if (Character.isWhitespace(nextChar)) { + return true; + } + } + return false; + + } + private int countBase64DecodedBytes(String theEncoded) { Base64InputStream inputStream = new Base64InputStream(new ByteArrayInputStream(theEncoded.getBytes(StandardCharsets.UTF_8))); @@ -2615,7 +2673,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkFixedValue(errors, path + ".code", focus.getNamedChild("code"), fixed.getCodeElement(), fixedSource, "code", focus, pattern); } - private void checkQuantity(List theErrors, String thePath, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) { + private void checkQuantity(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, NodeStack theStack) { + String value = element.hasChild("value") ? element.getNamedChild("value").getValue() : null; String unit = element.hasChild("unit") ? element.getNamedChild("unit").getValue() : null; String system = element.hasChild("system") ? element.getNamedChild("system").getValue() : null; String code = element.hasChild("code") ? element.getNamedChild("code").getValue() : null; @@ -2623,24 +2682,122 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // todo: allowedUnits http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits - codeableConcept, or canonical(ValueSet) // todo: http://hl7.org/fhir/StructureDefinition/iso21090-PQ-translation - if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { - String dec = element.getChildValue("value"); - int dp = dec.contains(".") ? dec.substring(dec.indexOf(".")+1).length() : 0; + if (!Utilities.noString(value) && definition.hasExtension("http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")) { + int dp = value.contains(".") ? value.substring(value.indexOf(".")+1).length() : 0; int def = Integer.parseInt(ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces")); - rule(theErrors, IssueType.STRUCTURE, element.line(), element.col(), thePath, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); + rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, dp <= def, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS, dp, def); } - + if (system != null || code != null ) { - checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, null, unit); + checkCodedElement(errors, path, element, theProfile, definition, false, false, theStack, code, system, null, unit); } - + if (code != null && "http://unitsofmeasure.org".equals(system)) { int b = code.indexOf("{"); int e = code.indexOf("}"); if (b >= 0 && e > 0 && b < e) { - bpCheck(theErrors, IssueType.BUSINESSRULE, element.line(), element.col(), thePath, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)); + bpCheck(errors, IssueType.BUSINESSRULE, element.line(), element.col(), path, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1)); } } + + if (definition.hasMinValue()) { + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_VALUE) && + rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_QTY, definition.getMinValue().fhirType())) { + Quantity min = definition.getMinValueQuantity(); + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_SYSTEM) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_SYSTEM) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, system.equals(min.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_SYSTEM_MISMATCH, system, min.getSystem()) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(min.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CODE) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_NO_CODE) && + rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMinValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_VALUE)) { + if (code.equals(min.getCode())) { + // straight value comparison + rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(value, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG, value, min.getValue().toString()); + } else if ("http://unitsofmeasure.org".equals(system)) { + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_NO_UCUM_SVC)) { + Decimal v = convertUcumValue(value, code, min.getCode()); + if (rule(errors, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_MIN_NO_CONVERT, value, code, min.getCode())) { + rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMinValue(v, min.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_VALUE_WRONG_UCUM, value, code, min.getValue().toString(), min.getCode()); + } + } + } else { + warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MIN_CODE_MISMATCH, code, min.getCode()); + } + } + } + } + + if (definition.hasMaxValue()) { + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(value), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_VALUE) && + rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValue() instanceof Quantity, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_QTY, definition.getMaxValue().fhirType())) { + Quantity max = definition.getMaxValueQuantity(); + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_SYSTEM) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(system), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_SYSTEM) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, system.equals(max.getSystem()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_SYSTEM_MISMATCH, system, max.getSystem()) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(max.getCode()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CODE) && + warning(errors, IssueType.INVALID, element.line(), element.col(), path, !Utilities.noString(code), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_NO_CODE) && + rule(errors, IssueType.INVALID, element.line(), element.col(), path, definition.getMaxValueQuantity().hasValue(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_VALUE)) { + if (code.equals(max.getCode())) { + // straight value comparison + rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(value, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG, value, max.getValue().toString()); + } else if ("http://unitsofmeasure.org".equals(system)) { + if (warning(errors, IssueType.INVALID, element.line(), element.col(), path, context.getUcumService() != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_NO_UCUM_SVC)) { + Decimal v = convertUcumValue(value, code, max.getCode()); + if (rule(errors, IssueType.INVALID, element.line(), element.col(), path, v != null, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_MIN_NO_CONVERT, value, code, max.getCode())) { + rule(errors, IssueType.INVALID, element.line(), element.col(), path, checkDecimalMaxValue(v, max.getValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_VALUE_WRONG_UCUM, value, code, max.getValue().toString(), max.getCode()); + } + } + } else { + warning(errors, IssueType.INVALID, element.line(), element.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_MAX_CODE_MISMATCH, code, max.getCode()); + } + } + } + } + } + + private Decimal convertUcumValue(String value, String code, String minCode) { + try { + Decimal v = new Decimal(value); + return context.getUcumService().convert(v, code, minCode); + } catch (Exception e) { + return null; + } + } + + private boolean checkDecimalMaxValue(Decimal value, BigDecimal min) { + try { + Decimal m = new Decimal(min.toString()); + return value.comparesTo(m) <= 0; + } catch (Exception e) { + return false; // this will be another error somewhere else? + } + } + + private boolean checkDecimalMaxValue(String value, BigDecimal min) { + try { + BigDecimal v = new BigDecimal(value); + return v.compareTo(min) <= 0; + } catch (Exception e) { + return false; // this will be another error somewhere else + } + } + + private boolean checkDecimalMinValue(Decimal value, BigDecimal min) { + try { + Decimal m = new Decimal(min.toString()); + return value.comparesTo(m) >= 0; + } catch (Exception e) { + return false; // this will be another error somewhere else? + } + } + + private boolean checkDecimalMinValue(String value, BigDecimal min) { + try { + BigDecimal v = new BigDecimal(value); + return v.compareTo(min) >= 0; + } catch (Exception e) { + return false; // this will be another error somewhere else + } } private void checkAttachment(List errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) { @@ -5408,8 +5565,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), resourceName.equals(defn.getType()), I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE, defn.getType(), resourceName, defn.getUrl())) { start(hostContext, errors, element, element, defn, stack); // root is both definition and type - } else { - System.out.println("what?"); } } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java index 73c70bda2..947f3ec59 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/QuestionnaireValidator.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.attoparser.config.ParseConfiguration.ElementBalancing; import org.hl7.fhir.convertors.conv10_50.VersionConvertor_10_50; import org.hl7.fhir.convertors.conv14_50.VersionConvertor_14_50; import org.hl7.fhir.convertors.conv30_50.VersionConvertor_30_50; @@ -40,6 +41,8 @@ import org.hl7.fhir.r5.model.TimeType; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.XVerExtensionManager; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; +import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationContextResourceProxy; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.i18n.I18nConstants; @@ -52,6 +55,7 @@ import org.hl7.fhir.validation.cli.utils.QuestionnaireMode; import org.hl7.fhir.validation.TimeTracker; import org.hl7.fhir.validation.instance.EnableWhenEvaluator; import org.hl7.fhir.validation.instance.EnableWhenEvaluator.QStack; +import org.hl7.fhir.validation.instance.type.QuestionnaireValidator.ElementWithIndex; import org.hl7.fhir.validation.instance.type.QuestionnaireValidator.QuestionnaireWithContext; import org.hl7.fhir.validation.instance.utils.NodeStack; import org.hl7.fhir.validation.instance.utils.ValidatorHostContext; @@ -60,6 +64,26 @@ import ca.uhn.fhir.util.ObjectUtil; public class QuestionnaireValidator extends BaseValidator { + public class ElementWithIndex { + + private Element element; + private int index; + + public ElementWithIndex(Element element, int index) { + this.element = element; + this.index = index; + } + + public Element getElement() { + return element; + } + + public int getIndex() { + return index; + } + + } + public static class QuestionnaireWithContext { private Questionnaire q; private Element container; @@ -88,7 +112,7 @@ public class QuestionnaireValidator extends BaseValidator { public Questionnaire q() { return q; } - + } private EnableWhenEvaluator myEnableWhenEvaluator; @@ -263,8 +287,9 @@ public class QuestionnaireValidator extends BaseValidator { if (answers.size() > 1) rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), I18nConstants.QUESTIONNAIRE_QR_ITEM_ONLYONEA); + int i = 0; for (Element answer : answers) { - NodeStack ns = stack.push(answer, -1, null, null); + NodeStack ns = stack.push(answer, i, null, null); if (qItem.getType() != null) { switch (qItem.getType()) { case GROUP: @@ -337,12 +362,15 @@ public class QuestionnaireValidator extends BaseValidator { case NULL: // no validation break; + case QUESTION: + throw new Error("Shouldn't get here?"); } } if (qItem.getType() != QuestionnaireItemType.GROUP) { // if it's a group, we already have an error before getting here, so no need to hammer away on that validateQuestionannaireResponseItems(hostContext, qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot, qstack); } + i++; } if (qItem.getType() == null) { fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTYPE, qItem.getLinkId()); @@ -363,14 +391,13 @@ public class QuestionnaireValidator extends BaseValidator { return !answers.isEmpty() || !qItem.getRequired() || qItem.getType() == QuestionnaireItemType.GROUP; } - private void validateQuestionnaireResponseItem(ValidatorHostContext hostcontext, QuestionnaireWithContext qsrc, QuestionnaireItemComponent qItem, List errors, List elements, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) { - if (elements.size() > 1) - rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), I18nConstants.QUESTIONNAIRE_QR_ITEM_ONLYONEI, qItem.getLinkId()); - int i = 0; - for (Element element : elements) { - NodeStack ns = stack.push(element, i, null, null); - validateQuestionnaireResponseItem(hostcontext, qsrc, qItem, errors, element, ns, inProgress, questionnaireResponseRoot, qstack.push(qItem, element)); - i++; + private void validateQuestionnaireResponseItem(ValidatorHostContext hostcontext, QuestionnaireWithContext qsrc, QuestionnaireItemComponent qItem, List errors, List elements, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QStack qstack) { + if (elements.size() > 1) { + rule(errors, IssueType.INVALID, elements.get(1).getElement().line(), elements.get(1).getElement().col(), stack.getLiteralPath(), qItem.getRepeats(), I18nConstants.QUESTIONNAIRE_QR_ITEM_ONLYONEI, qItem.getLinkId()); + } + for (ElementWithIndex element : elements) { + NodeStack ns = stack.push(element.getElement(), element.getIndex(), null, null); + validateQuestionnaireResponseItem(hostcontext, qsrc, qItem, errors, element.getElement(), ns, inProgress, questionnaireResponseRoot, qstack.push(qItem, element.getElement())); } } @@ -386,8 +413,9 @@ public class QuestionnaireValidator extends BaseValidator { List items = new ArrayList(); element.getNamedChildren("item", items); // now, sort into stacks - Map> map = new HashMap>(); + Map> map = new HashMap>(); int lastIndex = -1; + int counter = 0; for (Element item : items) { String linkId = item.getNamedChildValue("linkId"); if (rule(errors, IssueType.REQUIRED, item.line(), item.col(), stack.getLiteralPath(), !Utilities.noString(linkId), I18nConstants.QUESTIONNAIRE_QR_ITEM_NOLINKID)) { @@ -396,7 +424,7 @@ public class QuestionnaireValidator extends BaseValidator { QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId); if (qItem != null) { rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, misplacedItemError(qItem)); - NodeStack ns = stack.push(item, -1, null, null); + NodeStack ns = stack.push(item, counter, null, null); validateQuestionnaireResponseItem(hostContext, qsrc, qItem, errors, item, ns, inProgress, questionnaireResponseRoot, qstack.push(qItem, item)); } else rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTFOUND, linkId); @@ -407,29 +435,28 @@ public class QuestionnaireValidator extends BaseValidator { // If an item has a child called "linkId" but no child called "answer", // we'll treat it as not existing for the purposes of enableWhen validation if (item.hasChildren("answer") || item.hasChildren("item")) { - List mapItem = map.computeIfAbsent(linkId, key -> new ArrayList<>()); - mapItem.add(item); + List mapItem = map.computeIfAbsent(linkId, key -> new ArrayList<>()); + mapItem.add(new ElementWithIndex(item, counter)); } } } + counter++; } // ok, now we have a list of known items, grouped by linkId. We've made an error for anything out of order for (QuestionnaireItemComponent qItem : qItems) { - List mapItem = map.get(qItem.getLinkId()); + List mapItem = map.get(qItem.getLinkId()); validateQuestionnaireResponseItem(hostContext, qsrc, errors, element, stack, inProgress, questionnaireResponseRoot, qItem, mapItem, qstack); } } - public void validateQuestionnaireResponseItem(ValidatorHostContext hostContext, QuestionnaireWithContext qsrc, List errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QuestionnaireItemComponent qItem, List mapItem, QStack qstack) { + public void validateQuestionnaireResponseItem(ValidatorHostContext hostContext, QuestionnaireWithContext qsrc, List errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot, QuestionnaireItemComponent qItem, List mapItem, QStack qstack) { boolean enabled = myEnableWhenEvaluator.isQuestionEnabled(hostContext, qItem, qstack, fpe); if (mapItem != null) { if (!enabled) { - int i = 0; - for (Element e : mapItem) { - NodeStack ns = stack.push(e, i, e.getProperty().getDefinition(), e.getProperty().getDefinition()); - rule(errors, IssueType.INVALID, e.line(), e.col(), ns.getLiteralPath(), enabled, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTENABLED2, qItem.getLinkId()); - i++; + for (ElementWithIndex e : mapItem) { + NodeStack ns = stack.push(e.getElement(), e.getElement().getIndex(), e.getElement().getProperty().getDefinition(), e.getElement().getProperty().getDefinition()); + rule(errors, IssueType.INVALID, e.getElement().line(), e.getElement().col(), ns.getLiteralPath(), enabled, I18nConstants.QUESTIONNAIRE_QR_ITEM_NOTENABLED2, qItem.getLinkId()); } } @@ -516,7 +543,8 @@ public class QuestionnaireValidator extends BaseValidator { } long t = System.nanoTime(); - ValidationResult res = context.validateCode(new ValidationOptions(stack.getWorkingLang()), c, vs); + ValidationContextCarrier vc = makeValidationContext(errors, qSrc); + ValidationResult res = context.validateCode(new ValidationOptions(stack.getWorkingLang()), c, vs, vc); timeTracker.tx(t, "vc "+c.getSystem()+"#"+c.getCode()+" '"+c.getDisplay()+"'"); if (!res.isOk()) { txRule(errors, res.getTxLink(), IssueType.CODEINVALID, value.line(), value.col(), stack.getLiteralPath(), false, I18nConstants.QUESTIONNAIRE_QR_ITEM_BADOPTION, c.getSystem(), c.getCode()); @@ -529,6 +557,16 @@ public class QuestionnaireValidator extends BaseValidator { } } + private ValidationContextCarrier makeValidationContext(List errors, QuestionnaireWithContext qSrc) { + ValidationContextCarrier vc = new ValidationContextCarrier(); + if (qSrc.container == null) { + vc.getResources().add(new ValidationContextResourceProxy(qSrc.q)); + } else { + vc.getResources().add(new ValidationContextResourceProxy(errors, qSrc.containerPath, qSrc.container, this)); + } + return vc; + } + private void validateAnswerCode(List errors, Element answer, NodeStack stack, QuestionnaireWithContext qSrc, QuestionnaireItemComponent qItem, boolean theOpenChoice) { Element v = answer.getNamedChild("valueCoding"); NodeStack ns = stack.push(v, -1, null, null); diff --git a/pom.xml b/pom.xml index daff785df..6f274ab64 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ HAPI FHIR --> org.hl7.fhir.core - 5.6.8-SNAPSHOT + 5.6.11-SNAPSHOT pom 5.1.0 - 1.1.79 + 1.1.82 5.7.1 1.7.1 3.0.0-M5