* better validation of external references. Note: this is a potentially significant change: things that were called ok before may not be now, and things that were not ok before may become so, depending on the interplay between this and auto-load, further work may be needed here

* Support better validation of version specific profiles in meta.profile. This may also find new errors that were not previously being found
* Support auto-determination of the version of FHIR to use when using the java validator
* auto-load packages from the package server when references to profiles etc are encountered
* look for references inside other parameters in Parameters resource
This commit is contained in:
Grahame Grieve 2020-08-03 17:40:21 +10:00
parent 7616b55213
commit 5299bbe16b
13 changed files with 653 additions and 105 deletions

View File

@ -0,0 +1,18 @@
Validator Fixes:
* Support auto-determination of the version of FHIR to use when using the java validator
* auto-load packages from the package server when references to profiles etc are encountered
* Support better validation of version specific profiles in meta.profile
* look for references inside other parameters in Parameters resource
Other Code changes:
* Rendering: add rendering for Parameters resources
* Rendering: refactor of resource resolution code to support Parameters
* General clean up of rendering consistency & implement additional details when rendering (including patient summary)
* Rendering: major overhaul of DiagnosticReport rendering
* Fix NPE bug in value set comparison
TODO before commit:
* check version of contained resources
* review validation of CanonicalResource.url

View File

@ -267,6 +267,11 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
return res; return res;
} }
public static SimpleWorkerContext fromNothing() throws FileNotFoundException, FHIRException, IOException {
SimpleWorkerContext res = new SimpleWorkerContext();
return res;
}
private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException { private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
if (name.endsWith(".xml")) if (name.endsWith(".xml"))
loadFromFile(stream, name, loader, filter); loadFromFile(stream, name, loader, filter);

View File

@ -3,46 +3,58 @@ package org.hl7.fhir.r5.renderers.utils;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContextType;
import org.w3c.dom.Element; import org.w3c.dom.Element;
public class Resolver { public class Resolver {
public enum ResourceContextType {
PARAMETERS, BUNDLE
}
public interface IReferenceResolver { public interface IReferenceResolver {
ResourceWithReference resolve(RenderingContext context, String url); ResourceWithReference resolve(RenderingContext context, String url);
} }
public static class ResourceContext { public static class ResourceContext {
Bundle bundleResource; private ResourceContextType type;
org.hl7.fhir.r5.elementmodel.Element bundleElement; private Resource containerResource;
private org.hl7.fhir.r5.elementmodel.Element containerElement;
DomainResource resourceResource; DomainResource resourceResource;
org.hl7.fhir.r5.elementmodel.Element resourceElement; org.hl7.fhir.r5.elementmodel.Element resourceElement;
public ResourceContext(Bundle bundle, DomainResource dr) { public ResourceContext(ResourceContextType type, Resource bundle, DomainResource dr) {
super(); super();
this.bundleResource = bundle; this.type = type;
this.containerResource = bundle;
this.resourceResource = dr; this.resourceResource = dr;
} }
public ResourceContext(org.hl7.fhir.r5.elementmodel.Element bundle, org.hl7.fhir.r5.elementmodel.Element dr) { public ResourceContext(ResourceContextType type, org.hl7.fhir.r5.elementmodel.Element bundle, org.hl7.fhir.r5.elementmodel.Element dr) {
this.bundleElement = bundle; super();
this.type = type;
this.containerElement = bundle;
this.resourceElement = dr; this.resourceElement = dr;
} }
public ResourceContext(Object bundle, Element doc) { // public ResourceContext(Object bundle, Element doc) {
// TODO Auto-generated constructor stub // // TODO Auto-generated constructor stub
} // }
public Bundle getBundleResource() { // public Bundle getBundleResource() {
return bundleResource; // return containerResource;
} // }
public org.hl7.fhir.r5.elementmodel.Element getBundleElement() {
return bundleElement;
}
// public org.hl7.fhir.r5.elementmodel.Element getBundleElement() {
// return containerElement;
// }
//
public DomainResource getResourceResource() { public DomainResource getResourceResource() {
return resourceResource; return resourceResource;
} }
@ -64,14 +76,28 @@ public class Resolver {
} }
return null; return null;
} }
if (bundleResource != null) { if (type == ResourceContextType.BUNDLE) {
for (BundleEntryComponent be : bundleResource.getEntry()) { if (containerResource != null) {
for (BundleEntryComponent be : ((Bundle) containerResource).getEntry()) {
if (be.getFullUrl().equals(value)) if (be.getFullUrl().equals(value))
return be; return be;
if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId()))
return be; return be;
} }
} }
}
if (type == ResourceContextType.PARAMETERS) {
if (containerResource != null) {
for (ParametersParameterComponent p : ((Parameters) containerResource).getParameter()) {
if (p.getResource() != null && value.equals(p.getResource().fhirType()+"/"+p.getResource().getId())) {
BundleEntryComponent be = new BundleEntryComponent();
be.setResource(p.getResource());
return be;
}
}
}
}
return null; return null;
} }
@ -85,8 +111,9 @@ public class Resolver {
} }
return null; return null;
} }
if (bundleElement != null) { if (type == ResourceContextType.BUNDLE) {
for (org.hl7.fhir.r5.elementmodel.Element be : bundleElement.getChildren("entry")) { if (containerElement != null) {
for (org.hl7.fhir.r5.elementmodel.Element be : containerElement.getChildren("entry")) {
org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource"); org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource");
if (value.equals(be.getChildValue("fullUrl"))) if (value.equals(be.getChildValue("fullUrl")))
return be; return be;
@ -94,6 +121,16 @@ public class Resolver {
return be; return be;
} }
} }
}
if (type == ResourceContextType.PARAMETERS) {
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 (value.equals(res.fhirType()+"/"+res.getChildValue("id")))
return p;
}
}
}
return null; return null;
} }
} }

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.utilities; package org.hl7.fhir.utilities;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.utilities.cache.NpmPackage;
/* /*
Copyright (c) 2011+, HL7, Inc. Copyright (c) 2011+, HL7, Inc.
@ -35,6 +36,22 @@ import org.hl7.fhir.exceptions.FHIRException;
public class VersionUtilities { public class VersionUtilities {
public static class VersionURLInfo {
private String version;
private String url;
public VersionURLInfo(String version, String url) {
super();
this.version = version;
this.url = url;
}
public String getVersion() {
return version;
}
public String getUrl() {
return url;
}
}
public static final String CURRENT_VERSION = "4.4"; public static final String CURRENT_VERSION = "4.4";
public static final String CURRENT_FULL_VERSION = "4.4.0"; public static final String CURRENT_FULL_VERSION = "4.4.0";
@ -225,4 +242,18 @@ public class VersionUtilities {
throw new FHIRException("Unknown version "+version); throw new FHIRException("Unknown version "+version);
} }
public static VersionURLInfo parseVersionUrl(String url) {
if (url.length() < 24) {
return null;
}
String v = url.substring(20, 24);
if (v.endsWith("/")) {
v = v.substring(0, v.length()-1);
if (Utilities.existsInList(v, "1.0", "1.4", "3.0", "4.0", "5.0", CURRENT_VERSION)) {
return new VersionURLInfo(v, "http://hl7.org/fhir/"+url.substring(24));
}
}
return null;
}
} }

View File

@ -1058,5 +1058,25 @@ public class NpmPackage {
return "fhir.core".equals(JSONUtil.str(npm, "type")); return "fhir.core".equals(JSONUtil.str(npm, "type"));
} }
public boolean hasCanonical(String url) {
if (url == null) {
return false;
}
String u = url.contains("|") ? url.substring(0, url.indexOf("|")) : url;
String v = url.contains("|") ? url.substring(url.indexOf("|")+1) : null;
NpmPackageFolder folder = folders.get("package");
if (folder != null) {
for (JsonElement e : folder.index.getAsJsonArray("files")) {
JsonObject o = (JsonObject) e;
if (u.equals(JSONUtil.str(o, "url"))) {
if (v == null || v.equals(JSONUtil.str(o, "version"))) {
return true;
}
}
}
}
return false;
}
} }

View File

@ -25,6 +25,7 @@ public class I18nConstants {
public static final String BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE = "Bundle_BUNDLE_Entry_NoFirstResource"; public static final String BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE = "Bundle_BUNDLE_Entry_NoFirstResource";
public static final String BUNDLE_BUNDLE_ENTRY_NOFULLURL = "Bundle_BUNDLE_Entry_NoFullUrl"; public static final String BUNDLE_BUNDLE_ENTRY_NOFULLURL = "Bundle_BUNDLE_Entry_NoFullUrl";
public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE = "Bundle_BUNDLE_Entry_NoProfile"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE = "Bundle_BUNDLE_Entry_NoProfile";
public static final String BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = "BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES";
public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound";
public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN = "Bundle_BUNDLE_Entry_Orphan"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN = "Bundle_BUNDLE_Entry_Orphan";
public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type"; public static final String BUNDLE_BUNDLE_ENTRY_TYPE = "Bundle_BUNDLE_Entry_Type";
@ -543,10 +544,14 @@ public class I18nConstants {
public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL";
public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META";
public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder";
public static final String VALIDATION_VAL_PROFILE_OTHER_VERSION = "VALIDATION_VAL_PROFILE_OTHER_VERSION";
public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OK = "VALIDATION_VAL_PROFILE_THIS_VERSION_OK";
public static final String VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = "VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER";
public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown"; public static final String VALIDATION_VAL_PROFILE_UNKNOWN = "Validation_VAL_Profile_Unknown";
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType";
public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2";
public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile"; public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile";
public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE";
public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER";
public static final String VALUESET_NO_SYSTEM_WARNING = "VALUESET_NO_SYSTEM_WARNING"; public static final String VALUESET_NO_SYSTEM_WARNING = "VALUESET_NO_SYSTEM_WARNING";

View File

@ -571,3 +571,7 @@ PACKAGE_VERSION_MISMATCH = FHIR Version mismatch in package {0}: version is {2}
VALUESET_REFERENCE_UNKNOWN = The value set import {0} could not be found so cannot be checked VALUESET_REFERENCE_UNKNOWN = The value set import {0} could not be found so cannot be checked
VALUESET_REFERENCE_INVALID_TYPE = The value set import {0} points to a resource of type {1} which is not valid VALUESET_REFERENCE_INVALID_TYPE = The value set import {0} points to a resource of type {1} which is not valid
SD_MUST_HAVE_DERIVATION = StructureDefinition {0} must have a derivation, since it has a baseDefinition SD_MUST_HAVE_DERIVATION = StructureDefinition {0} must have a derivation, since it has a baseDefinition
VALIDATION_VAL_PROFILE_OTHER_VERSION = Profile is for a different version of FHIR ({0}) so has been ignored
VALIDATION_VAL_PROFILE_THIS_VERSION_OK = Profile for this version of FHIR - all OK
VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER = Profile is for this version of FHIR, but is an invalid type {0}
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES = Multiple profiles found for contained resource. This is not supported at this time. (Type {0}: {1})

View File

@ -34,6 +34,7 @@ import org.hl7.fhir.r5.utils.*;
import org.hl7.fhir.r5.utils.IResourceValidator.*; import org.hl7.fhir.r5.utils.IResourceValidator.*;
import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices; import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices;
import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
@ -163,7 +164,42 @@ POSSIBILITY OF SUCH DAMAGE.
* @author Grahame Grieve * @author Grahame Grieve
* *
*/ */
public class ValidationEngine implements IValidatorResourceFetcher { public class ValidationEngine implements IValidatorResourceFetcher, IPackageInstaller {
public static class VersionSourceInformation {
private List<String> report = new ArrayList<>();
private List<String> versions = new ArrayList<>();
public void see(String version, String src) {
version = VersionUtilities.getMajMin(version);
report.add(src+": "+version);
if (!versions.contains(version)) {
versions.add(version);
Collections.sort(versions);
}
}
public boolean isEmpty() {
return versions.isEmpty();
}
public int size() {
return versions.size();
}
public String version() {
return versions.get(0);
}
public List<String> getReport() {
if (report.isEmpty()) {
report.add("(nothing found)");
}
return report;
}
}
public class ScanOutputItem { public class ScanOutputItem {
private String ref; private String ref;
@ -311,9 +347,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
public ValidationEngine() throws IOException { public ValidationEngine() throws IOException {
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
context = SimpleWorkerContext.fromNothing();
} }
public void setTerminologyServer(String src, String log, FhirPublication version) throws Exception { public void setTerminologyServer(String src, String log, FhirPublication version) throws FHIRException, URISyntaxException {
connectToTSServer(src, log, version); connectToTSServer(src, log, version);
} }
@ -342,7 +379,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.showTimes = showTimes; this.showTimes = showTimes;
} }
public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, boolean canRunWithoutTerminologyServer, String vString) throws Exception { public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, boolean canRunWithoutTerminologyServer, String vString) throws FHIRException, IOException, URISyntaxException {
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
loadCoreDefinitions(src, false); loadCoreDefinitions(src, false);
context.setCanRunWithoutTerminology(canRunWithoutTerminologyServer); context.setCanRunWithoutTerminology(canRunWithoutTerminologyServer);
@ -350,14 +387,14 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.version = vString; this.version = vString;
} }
public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, String vString) throws Exception { public ValidationEngine(String src, String txsrvr, String txLog, FhirPublication version, String vString) throws FHIRException, IOException, URISyntaxException {
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
loadCoreDefinitions(src, false); loadCoreDefinitions(src, false);
setTerminologyServer(txsrvr, txLog, version); setTerminologyServer(txsrvr, txLog, version);
this.version = vString; this.version = vString;
} }
public ValidationEngine(String src) throws Exception { public ValidationEngine(String src) throws FHIRException, IOException {
loadCoreDefinitions(src, false); loadCoreDefinitions(src, false);
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
} }
@ -370,7 +407,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.language = language; this.language = language;
} }
private void loadCoreDefinitions(String src, boolean recursive) throws Exception { private void loadCoreDefinitions(String src, boolean recursive) throws FHIRException, IOException {
if (pcm == null) { if (pcm == null) {
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
} }
@ -435,7 +472,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return ep; return ep;
} }
private byte[] loadProfileSource(String src) throws Exception { private byte[] loadProfileSource(String src) throws FHIRException, FileNotFoundException, IOException {
if (Utilities.noString(src)) { if (Utilities.noString(src)) {
throw new FHIRException("Profile Source '" + src + "' could not be processed"); throw new FHIRException("Profile Source '" + src + "' could not be processed");
} else if (src.startsWith("https:") || src.startsWith("http:")) { } else if (src.startsWith("https:") || src.startsWith("http:")) {
@ -447,13 +484,13 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
private byte[] loadProfileFromUrl(String src) throws Exception { private byte[] loadProfileFromUrl(String src) throws FHIRException {
try { try {
URL url = new URL(src+"?nocache=" + System.currentTimeMillis()); URL url = new URL(src+"?nocache=" + System.currentTimeMillis());
URLConnection c = url.openConnection(); URLConnection c = url.openConnection();
return IOUtils.toByteArray(c.getInputStream()); return IOUtils.toByteArray(c.getInputStream());
} catch (Exception e) { } catch (Exception e) {
throw new Exception("Unable to find definitions at URL '"+src+"': "+e.getMessage(), e); throw new FHIRException("Unable to find definitions at URL '"+src+"': "+e.getMessage(), e);
} }
} }
@ -464,9 +501,10 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return TextFile.fileToBytes(src); return TextFile.fileToBytes(src);
} }
/** explore should be true if we're trying to load an -ig parameter, and false if we're loading source **/ /** explore should be true if we're trying to load an -ig parameter, and false if we're loading source
* @throws IOException **/
private Map<String, byte[]> loadIgSource(String src, boolean recursive, boolean explore) throws Exception { private Map<String, byte[]> loadIgSource(String src, boolean recursive, boolean explore) throws FHIRException, IOException {
// src can be one of the following: // src can be one of the following:
// - a canonical url for an ig - this will be converted to a package id and loaded into the cache // - a canonical url for an ig - this will be converted to a package id and loaded into the cache
// - a package id for an ig - this will be loaded into the cache // - a package id for an ig - this will be loaded into the cache
@ -510,11 +548,60 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) { } else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) {
return fetchByPackage(src); return fetchByPackage(src);
} }
throw new Exception("Unable to find/resolve/read -ig "+src); throw new FHIRException("Unable to find/resolve/read -ig "+src);
}
private Map<String, byte[]> loadIgSourceForVersion(String src, boolean recursive, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException {
if (src.startsWith("https:") || src.startsWith("http:")) {
String v = null;
if (src.contains("|")) {
v = src.substring(src.indexOf("|")+1);
src = src.substring(0, src.indexOf("|"));
}
String pid = pcm.getPackageId(src);
if (!Utilities.noString(pid)) {
versions.see(fetchVersionByPackage(pid+(v == null ? "" : "#"+v)), "Package "+src);
return null;
} else {
return fetchVersionFromUrl(src+(v == null ? "" : "|"+v), explore, versions);
}
}
File f = new File(Utilities.path(src));
if (f.exists()) {
if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists()) {
versions.see(loadPackageForVersion(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz")), "Package "+src);
return null;
}
if (f.isDirectory() && new File(Utilities.path(src, "igpack.zip")).exists())
return readZip(new FileInputStream(Utilities.path(src, "igpack.zip")));
if (f.isDirectory() && new File(Utilities.path(src, "validator.pack")).exists())
return readZip(new FileInputStream(Utilities.path(src, "validator.pack")));
if (f.isDirectory())
return scanDirectory(f, recursive);
if (src.endsWith(".tgz")) {
versions.see(loadPackageForVersion(new FileInputStream(src), src), "Package "+src);
return null;
}
if (src.endsWith(".pack"))
return readZip(new FileInputStream(src));
if (src.endsWith("igpack.zip"))
return readZip(new FileInputStream(src));
FhirFormat fmt = checkIsResource(src);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), TextFile.fileToBytesNCS(src));
return res;
}
} else if ((src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) || src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX)) && !src.endsWith(".zip") && !src.endsWith(".tgz")) {
versions.see(fetchVersionByPackage(src), "Package "+src);
return null;
}
throw new FHIRException("Unable to find/resolve/read -ig "+src);
} }
private Map<String, byte[]> fetchFromUrl(String src, boolean explore) throws Exception { private Map<String, byte[]> fetchFromUrl(String src, boolean explore) throws FHIRException, IOException {
if (src.endsWith(".tgz")) if (src.endsWith(".tgz"))
return loadPackage(fetchFromUrlSpecific(src, false), src); return loadPackage(fetchFromUrlSpecific(src, false), src);
if (src.endsWith(".pack")) if (src.endsWith(".pack"))
@ -551,15 +638,59 @@ public class ValidationEngine implements IValidatorResourceFetcher {
res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt); res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt);
return res; return res;
} }
throw new Exception("Unable to find/resolve/read -ig "+src); throw new FHIRException("Unable to find/resolve/read -ig "+src);
} }
private InputStream fetchFromUrlSpecific(String source, boolean optional) throws Exception { private Map<String, byte[]> fetchVersionFromUrl(String src, boolean explore, VersionSourceInformation versions) throws FHIRException, IOException {
if (src.endsWith(".tgz")) {
versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false), src), "From Package "+src);
return null;
}
if (src.endsWith(".pack"))
return readZip(fetchFromUrlSpecific(src, false));
if (src.endsWith("igpack.zip"))
return readZip(fetchFromUrlSpecific(src, false));
InputStream stream = null;
if (explore) {
stream = fetchFromUrlSpecific(Utilities.pathURL(src, "package.tgz"), true);
if (stream != null) {
versions.see(loadPackageForVersion(stream, Utilities.pathURL(src, "package.tgz")), "From Package at "+src);
return null;
}
// todo: these options are deprecated - remove once all IGs have been rebuilt post R4 technical correction
stream = fetchFromUrlSpecific(Utilities.pathURL(src, "igpack.zip"), true);
if (stream != null)
return readZip(stream);
stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
if (stream != null)
return readZip(stream);
stream = fetchFromUrlSpecific(Utilities.pathURL(src, "validator.pack"), true);
//// -----
}
// ok, having tried all that... now we'll just try to access it directly
byte[] cnt;
if (stream == null)
cnt = fetchFromUrlSpecific(src, "application/json", true);
else
cnt = TextFile.streamToBytes(stream);
FhirFormat fmt = checkIsResource(cnt, src);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt);
return res;
}
throw new FHIRException("Unable to find/resolve/read -ig "+src);
}
private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException {
try { try {
URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); URL url = new URL(source+"?nocache=" + System.currentTimeMillis());
URLConnection c = url.openConnection(); URLConnection c = url.openConnection();
return c.getInputStream(); return c.getInputStream();
} catch (Exception e) { } catch (IOException e) {
if (optional) if (optional)
return null; return null;
else else
@ -567,13 +698,13 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional) throws Exception { private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional) throws FHIRException, IOException {
try { try {
URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); URL url = new URL(source+"?nocache=" + System.currentTimeMillis());
HttpURLConnection conn = (HttpURLConnection) url.openConnection(); HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept", contentType); conn.setRequestProperty("Accept", contentType);
return TextFile.streamToBytes(conn.getInputStream()); return TextFile.streamToBytes(conn.getInputStream());
} catch (Exception e) { } catch (IOException e) {
if (optional) if (optional)
return null; return null;
else else
@ -604,11 +735,15 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), "md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip"); return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), "md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip");
} }
private Map<String, byte[]> loadPackage(InputStream stream, String name) throws Exception { private Map<String, byte[]> loadPackage(InputStream stream, String name) throws FHIRException, IOException {
return loadPackage(NpmPackage.fromPackage(stream)); return loadPackage(NpmPackage.fromPackage(stream));
} }
public Map<String, byte[]> loadPackage(NpmPackage pi) throws Exception { private String loadPackageForVersion(InputStream stream, String name) throws FHIRException, IOException {
return NpmPackage.fromPackage(stream).fhirVersion();
}
public Map<String, byte[]> loadPackage(NpmPackage pi) throws FHIRException, IOException {
context.getLoadedPackages().add(pi.name()+"#"+pi.version()); context.getLoadedPackages().add(pi.name()+"#"+pi.version());
Map<String, byte[]> res = new HashMap<String, byte[]>(); Map<String, byte[]> res = new HashMap<String, byte[]>();
for (String s : pi.dependencies()) { for (String s : pi.dependencies()) {
@ -652,7 +787,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
System.out.println(message); System.out.println(message);
} }
private Map<String, byte[]> fetchByPackage(String src) throws Exception { private Map<String, byte[]> fetchByPackage(String src) throws FHIRException, IOException {
String id = src; String id = src;
String version = null; String version = null;
if (src.contains("#")) { if (src.contains("#")) {
@ -679,13 +814,46 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return loadPackage(pi); return loadPackage(pi);
} }
private Map<String, byte[]> resolvePackage(String id, String v) throws Exception { private String fetchVersionByPackage(String src) throws FHIRException, IOException {
String id = src;
String version = null;
if (src.contains("#")) {
id = src.substring(0, src.indexOf("#"));
version = src.substring(src.indexOf("#")+1);
}
if (pcm == null) {
log("Creating Package manager?");
pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
}
if (version == null) {
version = pcm.getLatestVersion(id);
}
NpmPackage pi = null;
if (version == null) {
pi = pcm.loadPackageFromCacheOnly(id);
if (pi != null)
log(" ... Using version "+pi.version());
} else
pi = pcm.loadPackageFromCacheOnly(id, version);
if (pi == null) {
return resolvePackageForVersion(id, version);
} else {
return pi.fhirVersion();
}
}
private Map<String, byte[]> resolvePackage(String id, String v) throws FHIRException, IOException {
NpmPackage pi = pcm.loadPackage(id, v); NpmPackage pi = pcm.loadPackage(id, v);
if (pi != null && v == null) if (pi != null && v == null)
log(" ... Using version "+pi.version()); log(" ... Using version "+pi.version());
return loadPackage(pi); return loadPackage(pi);
} }
private String resolvePackageForVersion(String id, String v) throws FHIRException, IOException {
NpmPackage pi = pcm.loadPackage(id, v);
return pi.fhirVersion();
}
public SimpleWorkerContext getContext() { public SimpleWorkerContext getContext() {
return context; return context;
} }
@ -758,7 +926,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
public void loadProfile(String src) throws Exception { public void loadProfile(String src) throws FHIRException, IOException {
if (context.hasResource(StructureDefinition.class, src)) if (context.hasResource(StructureDefinition.class, src))
return; return;
if (context.hasResource(ImplementationGuide.class, src)) if (context.hasResource(ImplementationGuide.class, src))
@ -770,7 +938,13 @@ public class ValidationEngine implements IValidatorResourceFetcher {
context.cacheResource(r); context.cacheResource(r);
} }
public void loadIg(String src, boolean recursive) throws IOException, FHIRException, Exception { public void scanForIgVersion(String src, boolean recursive, VersionSourceInformation versions) throws IOException, FHIRException, Exception {
Map<String, byte[]> source = loadIgSourceForVersion(src, recursive, true, versions);
if (source != null && source.containsKey("version.info"))
versions.see(readInfoVersion(source.get("version.info")), "version.info in "+src);
}
public void loadIg(String src, boolean recursive) throws IOException, FHIRException {
NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) ? pcm.loadPackage(src, null) : null; NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_REGEX) ? pcm.loadPackage(src, null) : null;
if (npm != null) { if (npm != null) {
for (String s : npm.dependencies()) { for (String s : npm.dependencies()) {
@ -834,7 +1008,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return r; return r;
} }
public Resource loadResourceByVersion(String version, byte[] content, String fn) throws IOException, Exception { public Resource loadResourceByVersion(String version, byte[] content, String fn) throws IOException, FHIRException {
Resource r; Resource r;
if (version.startsWith("3.0")) { if (version.startsWith("3.0")) {
org.hl7.fhir.dstu3.model.Resource res; org.hl7.fhir.dstu3.model.Resource res;
@ -845,7 +1019,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content)); res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content));
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
r = VersionConvertor_30_50.convertResource(res, false); r = VersionConvertor_30_50.convertResource(res, false);
} else if (version.startsWith("4.0")) { } else if (version.startsWith("4.0")) {
org.hl7.fhir.r4.model.Resource res; org.hl7.fhir.r4.model.Resource res;
@ -856,7 +1030,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn); res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
r = VersionConvertor_40_50.convertResource(res); r = VersionConvertor_40_50.convertResource(res);
} else if (version.startsWith("1.4")) { } else if (version.startsWith("1.4")) {
org.hl7.fhir.dstu2016may.model.Resource res; org.hl7.fhir.dstu2016may.model.Resource res;
@ -865,7 +1039,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".json") && !fn.endsWith("template.json")) else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content)); res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content));
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
r = VersionConvertor_14_50.convertResource(res); r = VersionConvertor_14_50.convertResource(res);
} else if (version.startsWith("1.0")) { } else if (version.startsWith("1.0")) {
org.hl7.fhir.dstu2.model.Resource res; org.hl7.fhir.dstu2.model.Resource res;
@ -874,7 +1048,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".json") && !fn.endsWith("template.json")) else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content)); res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content));
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5();
r = VersionConvertor_10_50.convertResource(res, advisor); r = VersionConvertor_10_50.convertResource(res, advisor);
} else if (version.equals(Constants.VERSION) || "current".equals(version)) { } else if (version.equals(Constants.VERSION) || "current".equals(version)) {
@ -887,9 +1061,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
r = new org.hl7.fhir.r5.utils.StructureMapUtilities(null).parse(new String(content), fn); r = new org.hl7.fhir.r5.utils.StructureMapUtilities(null).parse(new String(content), fn);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else } else
throw new Exception("Unsupported version "+version); throw new FHIRException("Unsupported version "+version);
return r; return r;
} }
@ -923,11 +1097,11 @@ public class ValidationEngine implements IValidatorResourceFetcher {
FhirFormat cntType = null; FhirFormat cntType = null;
} }
public Content loadContent(String source, String opName) throws Exception { public Content loadContent(String source, String opName) throws FHIRException, IOException {
Map<String, byte[]> s = loadIgSource(source, false, false); Map<String, byte[]> s = loadIgSource(source, false, false);
Content res = new Content(); Content res = new Content();
if (s.size() != 1) if (s.size() != 1)
throw new Exception("Unable to find resource " + source + " to "+opName); throw new FHIRException("Unable to find resource " + source + " to "+opName);
for (Entry<String, byte[]> t: s.entrySet()) { for (Entry<String, byte[]> t: s.entrySet()) {
res.focus = t.getValue(); res.focus = t.getValue();
if (t.getKey().endsWith(".json")) if (t.getKey().endsWith(".json"))
@ -939,13 +1113,13 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map")) else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map"))
res.cntType = FhirFormat.TEXT; res.cntType = FhirFormat.TEXT;
else else
throw new Exception("Todo: Determining resource type is not yet done"); throw new FHIRException("Todo: Determining resource type is not yet done");
} }
return res; return res;
} }
// testing entry point // testing entry point
public OperationOutcome validate(FhirFormat format, InputStream stream, List<String> profiles) throws Exception { public OperationOutcome validate(FhirFormat format, InputStream stream, List<String> profiles) throws FHIRException, IOException, EOperationOutcome {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
InstanceValidator validator = getValidator(); InstanceValidator validator = getValidator();
validator.validate(null, messages, stream, format, asSdList(profiles)); validator.validate(null, messages, stream, format, asSdList(profiles));
@ -966,13 +1140,13 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return list; return list;
} }
public OperationOutcome validate(String source, List<String> profiles) throws Exception { public OperationOutcome validate(String source, List<String> profiles) throws FHIRException, IOException {
List<String> l = new ArrayList<String>(); List<String> l = new ArrayList<String>();
l.add(source); l.add(source);
return (OperationOutcome)validate(l, profiles); return (OperationOutcome)validate(l, profiles);
} }
public List<ScanOutputItem> validateScan(List<String> sources, Set<String> guides) throws Exception { public List<ScanOutputItem> validateScan(List<String> sources, Set<String> guides) throws FHIRException, IOException, EOperationOutcome {
List<String> refs = new ArrayList<String>(); List<String> refs = new ArrayList<String>();
handleSources(sources, refs); handleSources(sources, refs);
@ -1048,7 +1222,28 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return null; return null;
} }
public Resource validate(List<String> sources, List<String> profiles) throws Exception { public void scanForVersions(List<String> sources, VersionSourceInformation versions) throws FHIRException, IOException {
List<String> refs = new ArrayList<String>();
handleSources(sources, refs);
for (String ref : refs) {
Content cnt = loadContent(ref, "validate");
String s = TextFile.bytesToString(cnt.focus);
if (s.contains("http://hl7.org/fhir/3.0")) {
versions.see("3.0", "Profile in "+ref);
}
if (s.contains("http://hl7.org/fhir/1.0")) {
versions.see("1.0", "Profile in "+ref);
}
if (s.contains("http://hl7.org/fhir/4.0")) {
versions.see("4.0", "Profile in "+ref);
}
if (s.contains("http://hl7.org/fhir/1.4")) {
versions.see("1.4", "Profile in "+ref);
}
}
}
public Resource validate(List<String> sources, List<String> profiles) throws FHIRException, IOException {
List<String> refs = new ArrayList<String>(); List<String> refs = new ArrayList<String>();
boolean asBundle = handleSources(sources, refs); boolean asBundle = handleSources(sources, refs);
Bundle results = new Bundle(); Bundle results = new Bundle();
@ -1065,7 +1260,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
results.addEntry().setResource(outcome); results.addEntry().setResource(outcome);
} catch (Exception e) { } catch (Exception e) {
System.out.println("Validation Infrastructure fail validating "+ref+": "+e.getMessage()); System.out.println("Validation Infrastructure fail validating "+ref+": "+e.getMessage());
throw e; throw new FHIRException(e);
} }
} }
if (asBundle) if (asBundle)
@ -1082,7 +1277,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
public OperationOutcome validateString(String location, String source, FhirFormat format, List<String> profiles) throws Exception { public OperationOutcome validateString(String location, String source, FhirFormat format, List<String> profiles) throws FHIRException, IOException, EOperationOutcome, SAXException {
return validate(location, source.getBytes(), format, profiles); return validate(location, source.getBytes(), format, profiles);
} }
@ -1136,14 +1331,14 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return isBundle; return isBundle;
} }
public OperationOutcome validate(byte[] source, FhirFormat cntType, List<String> profiles) throws Exception { public OperationOutcome validate(byte[] source, FhirFormat cntType, List<String> profiles) throws FHIRException, IOException, EOperationOutcome {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
InstanceValidator validator = getValidator(); InstanceValidator validator = getValidator();
validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles)); validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
return messagesToOutcome(messages); return messagesToOutcome(messages);
} }
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles) throws Exception { public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles) throws FHIRException, IOException, EOperationOutcome, SAXException {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (doNative) { if (doNative) {
if (cntType == FhirFormat.JSON) if (cntType == FhirFormat.JSON)
@ -1161,7 +1356,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return messagesToOutcome(messages); return messagesToOutcome(messages);
} }
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, IdStatus resourceIdRule, boolean anyExtensionsAllowed, BestPracticeWarningLevel bpWarnings, CheckDisplayOption displayOption) throws Exception { public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, IdStatus resourceIdRule, boolean anyExtensionsAllowed, BestPracticeWarningLevel bpWarnings, CheckDisplayOption displayOption) throws FHIRException, IOException, EOperationOutcome, SAXException {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (doNative) { if (doNative) {
if (cntType == FhirFormat.JSON) if (cntType == FhirFormat.JSON)
@ -1254,12 +1449,12 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return issue.getSeverity().toString()+" @ "+issue.getLocation() + " " +issue.getDetails().getText() +(source != null ? " (src = "+source+")" : ""); return issue.getSeverity().toString()+" @ "+issue.getLocation() + " " +issue.getDetails().getText() +(source != null ? " (src = "+source+")" : "");
} }
public org.hl7.fhir.r5.elementmodel.Element transform(String source, String map) throws Exception { public org.hl7.fhir.r5.elementmodel.Element transform(String source, String map) throws FHIRException, IOException {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
return transform(cnt.focus, cnt.cntType, map); return transform(cnt.focus, cnt.cntType, map);
} }
public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws Exception { public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws FHIRException, IOException {
List<Base> outputs = new ArrayList<Base>(); List<Base> outputs = new ArrayList<Base>();
StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs)); StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs));
@ -1299,7 +1494,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return Manager.build(getContext(), structureDefinition); return Manager.build(getContext(), structureDefinition);
} }
public DomainResource generate(String source, String version) throws Exception { public DomainResource generate(String source, String version) throws FHIRException, IOException, EOperationOutcome {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
Resource res = loadResourceByVersion(version, cnt.focus, source); Resource res = loadResourceByVersion(version, cnt.focus, source);
RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE);
@ -1307,25 +1502,25 @@ public class ValidationEngine implements IValidatorResourceFetcher {
return (DomainResource) res; return (DomainResource) res;
} }
public void convert(String source, String output) throws Exception { public void convert(String source, String output) throws FHIRException, IOException {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
Manager.compose(context, e, new FileOutputStream(output), (output.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML), OutputStyle.PRETTY, null); Manager.compose(context, e, new FileOutputStream(output), (output.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML), OutputStyle.PRETTY, null);
} }
public String evaluateFhirPath(String source, String expression) throws Exception { public String evaluateFhirPath(String source, String expression) throws FHIRException, IOException {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
FHIRPathEngine fpe = new FHIRPathEngine(context); FHIRPathEngine fpe = new FHIRPathEngine(context);
Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType); Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
return fpe.evaluateToString(e, expression); return fpe.evaluateToString(e, expression);
} }
public StructureDefinition snapshot(String source, String version) throws Exception { public StructureDefinition snapshot(String source, String version) throws FHIRException, IOException {
Content cnt = loadContent(source, "validate"); Content cnt = loadContent(source, "validate");
Resource res = loadResourceByVersion(version, cnt.focus, Utilities.getFileNameForName(source)); Resource res = loadResourceByVersion(version, cnt.focus, Utilities.getFileNameForName(source));
if (!(res instanceof StructureDefinition)) if (!(res instanceof StructureDefinition))
throw new Exception("Require a StructureDefinition for generating a snapshot"); throw new FHIRException("Require a StructureDefinition for generating a snapshot");
StructureDefinition sd = (StructureDefinition) res; StructureDefinition sd = (StructureDefinition) res;
StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
@ -1670,8 +1865,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
@Override @Override
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException { public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
if (!url.startsWith("http://hl7.org/fhir")) if (!url.startsWith("http://") && !url.startsWith("https://")) { // ignore these
return true; // we don't bother with those. return true;
}
if (context.fetchResource(Resource.class, url) != null) if (context.fetchResource(Resource.class, url) != null)
return true; return true;
if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi", "http://hl7.org/fhir/sid/icd-10", if (Utilities.existsInList(url, "http://hl7.org/fhir/sid/us-ssn", "http://hl7.org/fhir/sid/cvx", "http://hl7.org/fhir/sid/ndc", "http://hl7.org/fhir/sid/us-npi", "http://hl7.org/fhir/sid/icd-10",
@ -1679,6 +1875,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
"http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) { "http://hl7.org/fhir/workflow", "http://hl7.org/fhir/ConsentPolicy/opt-out", "http://hl7.org/fhir/ConsentPolicy/opt-in")) {
return true; return true;
} }
if (Utilities.existsInList(url, "http://loinc.org", "http://unitsofmeasure.org", "http://snomed.info/sct")) {
return true;
}
if (fetcher != null) { if (fetcher != null) {
return fetcher.resolveURL(appContext, path, url); return fetcher.resolveURL(appContext, path, url);
}; };
@ -1690,7 +1889,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
this.locale = locale; this.locale = locale;
} }
public void handleOutput(Resource r, String output, String version) throws Exception { public void handleOutput(Resource r, String output, String version) throws FHIRException, IOException {
if (output.startsWith("http://") || output.startsWith("http://")) { if (output.startsWith("http://") || output.startsWith("http://")) {
ByteArrayOutputStream bs = new ByteArrayOutputStream(); ByteArrayOutputStream bs = new ByteArrayOutputStream();
handleOutputToStream(r, output, bs, version); handleOutputToStream(r, output, bs, version);
@ -1719,7 +1918,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
} }
private void handleOutputToStream(Resource r, String fn, OutputStream s, String version) throws Exception { private void handleOutputToStream(Resource r, String fn, OutputStream s, String version) throws FHIRException, IOException {
if (fn.endsWith(".html") || fn.endsWith(".htm") && r instanceof DomainResource) if (fn.endsWith(".html") || fn.endsWith(".htm") && r instanceof DomainResource)
new XhtmlComposer(XhtmlComposer.HTML, true).compose(s, ((DomainResource) r).getText().getDiv()); new XhtmlComposer(XhtmlComposer.HTML, true).compose(s, ((DomainResource) r).getText().getDiv());
else if (version.startsWith("3.0")) { else if (version.startsWith("3.0")) {
@ -1731,7 +1930,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false); TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else if (version.startsWith("4.0")) { } else if (version.startsWith("4.0")) {
org.hl7.fhir.r4.model.Resource res = VersionConvertor_40_50.convertResource(r); org.hl7.fhir.r4.model.Resource res = VersionConvertor_40_50.convertResource(r);
if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
@ -1741,7 +1940,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false); TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else if (version.startsWith("1.4")) { } else if (version.startsWith("1.4")) {
org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertor_14_50.convertResource(r); org.hl7.fhir.dstu2016may.model.Resource res = VersionConvertor_14_50.convertResource(r);
if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
@ -1749,7 +1948,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".json") && !fn.endsWith("template.json")) else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
new org.hl7.fhir.dstu2016may.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res); new org.hl7.fhir.dstu2016may.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2016may.formats.IParser.OutputStyle.PRETTY).compose(s, res);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else if (version.startsWith("1.0")) { } else if (version.startsWith("1.0")) {
VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5(); VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5();
org.hl7.fhir.dstu2.model.Resource res = VersionConvertor_10_50.convertResource(r, advisor); org.hl7.fhir.dstu2.model.Resource res = VersionConvertor_10_50.convertResource(r, advisor);
@ -1758,7 +1957,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".json") && !fn.endsWith("template.json")) else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res); new org.hl7.fhir.dstu2.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(s, res);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else if (version.equals(Constants.VERSION)) { } else if (version.equals(Constants.VERSION)) {
if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r);
@ -1767,9 +1966,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
else if (fn.endsWith(".txt") || fn.endsWith(".map") ) else if (fn.endsWith(".txt") || fn.endsWith(".map") )
TextFile.stringToStream(org.hl7.fhir.r5.utils.StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false); TextFile.stringToStream(org.hl7.fhir.r5.utils.StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false);
else else
throw new Exception("Unsupported format for "+fn); throw new FHIRException("Unsupported format for "+fn);
} else } else
throw new Exception("Encounted unsupported configured version "+version+" loading "+fn); throw new FHIRException("Encounted unsupported configured version "+version+" loading "+fn);
s.close(); s.close();
} }
@ -1833,7 +2032,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} else if (VersionUtilities.isR4Ver(version)) { } else if (VersionUtilities.isR4Ver(version)) {
return convertVersionNativeR4(targetVer, cnt, format); return convertVersionNativeR4(targetVer, cnt, format);
} else { } else {
throw new Exception("Source version not supported yet: "+version); throw new FHIRException("Source version not supported yet: "+version);
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("Conversion failed using Java convertor: "+e.getMessage()); System.out.println("Conversion failed using Java convertor: "+e.getMessage());
@ -1940,7 +2139,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); throw new FHIRException("Unsupported output format: "+cnt.cntType.toString());
} }
} else { } else {
throw new Exception("Target Version not supported yet: "+targetVer); throw new FHIRException("Target Version not supported yet: "+targetVer);
} }
} }
@ -2009,7 +2208,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); throw new FHIRException("Unsupported output format: "+cnt.cntType.toString());
} }
} else { } else {
throw new Exception("Target Version not supported yet: "+targetVer); throw new FHIRException("Target Version not supported yet: "+targetVer);
} }
} }
@ -2078,7 +2277,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); throw new FHIRException("Unsupported output format: "+cnt.cntType.toString());
} }
} else { } else {
throw new Exception("Target Version not supported yet: "+targetVer); throw new FHIRException("Target Version not supported yet: "+targetVer);
} }
} }
@ -2147,7 +2346,7 @@ public class ValidationEngine implements IValidatorResourceFetcher {
throw new FHIRException("Unsupported output format: "+cnt.cntType.toString()); throw new FHIRException("Unsupported output format: "+cnt.cntType.toString());
} }
} else { } else {
throw new Exception("Target Version not supported yet: "+targetVer); throw new FHIRException("Target Version not supported yet: "+targetVer);
} }
} }
@ -2166,4 +2365,9 @@ public class ValidationEngine implements IValidatorResourceFetcher {
} }
public FilesystemPackageCacheManager getPcm() {
return pcm;
}
} }

View File

@ -65,6 +65,7 @@ import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.validation.ValidationEngine.VersionSourceInformation;
import org.hl7.fhir.validation.cli.ValidatorGui; import org.hl7.fhir.validation.cli.ValidatorGui;
import org.hl7.fhir.validation.cli.services.ComparisonService; import org.hl7.fhir.validation.cli.services.ComparisonService;
import org.hl7.fhir.validation.cli.services.ValidationService; import org.hl7.fhir.validation.cli.services.ValidationService;
@ -171,6 +172,10 @@ public class Validator {
Display.printCliArgumentsAndInfo(args); Display.printCliArgumentsAndInfo(args);
cliContext = Params.loadCliContext(args); cliContext = Params.loadCliContext(args);
if (cliContext.getSv() == null) {
cliContext.setSv(determineVersion(cliContext));
}
// Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long). Version gets spit out a couple of lines later after we've loaded the context // Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long). Version gets spit out a couple of lines later after we've loaded the context
String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv()); String definitions = VersionUtilities.packageForVersion(cliContext.getSv()) + "#" + VersionUtilities.getCurrentVersion(cliContext.getSv());
ValidationEngine validator = ValidationService.getValidator(cliContext, definitions); ValidationEngine validator = ValidationService.getValidator(cliContext, definitions);
@ -204,5 +209,27 @@ public class Validator {
} }
} }
} }
} }
public static String determineVersion(CliContext cliContext) throws Exception {
if (cliContext.getMode() != EngineMode.VALIDATION) {
return "current";
}
System.out.println("Scanning for versions (no -version parameter):");
VersionSourceInformation versions = ValidationService.scanForVersions(cliContext);
for (String s : versions.getReport()) {
System.out.println(" "+s);
}
if (versions.isEmpty()) {
System.out.println("-> Using Default version '"+VersionUtilities.CURRENT_VERSION+"'");
return "current";
}
if (versions.size() == 1) {
System.out.println("-> use version "+versions.version());
return versions.version();
}
throw new Exception("-> Multiple versions found. Specify a particular version using the -version parameter");
}
} }

View File

@ -36,7 +36,7 @@ public class CliContext {
@JsonProperty("txServer") @JsonProperty("txServer")
private String txServer = "http://tx.fhir.org"; private String txServer = "http://tx.fhir.org";
@JsonProperty("sv") @JsonProperty("sv")
private String sv = "current"; private String sv = null;
@JsonProperty("txLog") @JsonProperty("txLog")
private String txLog = null; private String txLog = null;
@JsonProperty("mapLog") @JsonProperty("mapLog")

View File

@ -0,0 +1,109 @@
package org.hl7.fhir.validation.cli.services;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Locale;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.cache.BasePackageCacheManager;
import org.hl7.fhir.utilities.cache.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.cache.NpmPackage;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
public interface IPackageInstaller {
public void loadIg(String src, boolean recursive) throws IOException, FHIRException;
}
private BasePackageCacheManager pcm;
private IWorkerContext context;
private IPackageInstaller installer;
public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) {
super();
this.pcm = pcm;
this.context = context;
this.installer = installer;
}
@Override
public Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
throw new Error("Not done yet");
}
@Override
public ReferenceValidationPolicy validationPolicy(Object appContext, String path, String url) {
throw new Error("Not done yet");
}
@Override
public boolean resolveURL(Object appContext, String path, String url) throws IOException, FHIRException {
if (!Utilities.isAbsoluteUrl(url)) {
return false;
}
// if we've got to here, it's a reference to a FHIR URL. We're going to try to resolve it on the fly
// first possibility: it's a reference to a version specific URL http://hl7.org/fhir/X.X/...
VersionURLInfo vu = VersionUtilities.parseVersionUrl(url);
if (vu != null) {
NpmPackage pi = pcm.loadPackage(VersionUtilities.packageForVersion(vu.getVersion()), VersionUtilities.getCurrentVersion(vu.getVersion()));
return pi.hasCanonical(vu.getUrl());
}
// ok maybe it's a reference to a package we know
String base = findBaseUrl(url);
String pid = pcm.getPackageId(base);
if (url.contains("|")) {
pid = pid+"#"+url.substring(url.indexOf("|")+1);
}
if (pid != null) {
installer.loadIg(pid, false);
NpmPackage pi = pcm.loadPackage(pid);
return pi.hasCanonical(url);
}
if (!url.startsWith("http://hl7.org/fhir")) {
return true; // we don't bother with those in the standalone validator - we assume they are valid
}
// we assume it's invalid at this point
return false;
}
private String findBaseUrl(String url) {
String[] p = url.split("\\/");
for (int i = 1; i< p.length; i++) {
if (Utilities.existsInList(p[i], context.getResourceNames())) {
StringBuilder b = new StringBuilder(p[0]);
for (int j = 1; j < i; j++) {
b.append("/");
b.append(p[j]);
}
return b.toString();
}
}
return null;
}
@Override
public byte[] fetchRaw(String url) throws MalformedURLException, IOException {
throw new Error("Not done yet");
}
@Override
public void setLocale(Locale locale) {
throw new Error("Not done yet");
}
}

View File

@ -11,6 +11,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.validation.ValidationEngine; import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidationEngine.VersionSourceInformation;
import org.hl7.fhir.validation.cli.model.*; import org.hl7.fhir.validation.cli.model.*;
import java.io.File; import java.io.File;
@ -52,6 +53,15 @@ public class ValidationService {
return response; return response;
} }
public static VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception {
VersionSourceInformation versions = new VersionSourceInformation();
ValidationEngine ve = new ValidationEngine();
for (String src : cliContext.getIgs()) {
ve.scanForIgVersion(src, cliContext.isRecursive(), versions);
}
ve.scanForVersions(cliContext.getSources(), versions);
return versions;
}
public static void validateSources(CliContext cliContext, ValidationEngine validator, long loadStart) throws Exception { public static void validateSources(CliContext cliContext, ValidationEngine validator, long loadStart) throws Exception {
validator.doneLoading(loadStart); validator.doneLoading(loadStart);
if (cliContext.getProfiles().size() > 0) { if (cliContext.getProfiles().size() > 0) {
@ -205,6 +215,7 @@ public class ValidationService {
validator.setSecurityChecks(cliContext.isSecurityChecks()); validator.setSecurityChecks(cliContext.isSecurityChecks());
validator.setCrumbTrails(cliContext.isCrumbTrails()); validator.setCrumbTrails(cliContext.isCrumbTrails());
validator.setShowTimes(cliContext.isShowTimes()); validator.setShowTimes(cliContext.isShowTimes());
validator.setFetcher(new StandAloneValidatorFetcher(validator.getPcm(), validator.getContext(), validator));
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching()); TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
return validator; return validator;
} }

View File

@ -151,6 +151,7 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.Utilities.DecimalStatus; import org.hl7.fhir.utilities.Utilities.DecimalStatus;
import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
@ -592,8 +593,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
throw new FHIRException(e1); throw new FHIRException(e1);
} }
timeTracker.load(t); timeTracker.load(t);
if (e != null) if (e != null) {
validate(appContext, errors, e, profiles); validate(appContext, errors, e, profiles);
}
return e; return e;
} }
@ -1856,7 +1858,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (fetcher != null) { if (fetcher != null) {
boolean found; boolean found;
try { try {
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com"))) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url); found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) || (url.startsWith("http://hl7.org/fhir/tools")) || fetcher.resolveURL(appContext, path, url);
} catch (IOException e1) { } catch (IOException e1) {
found = false; found = false;
} }
@ -2928,7 +2930,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return rr; return rr;
} }
} }
if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY) { if (stack.getElement().getSpecial() == SpecialElement.BUNDLE_ENTRY || stack.getElement().getSpecial() == SpecialElement.PARAMETER) {
return null; // we don't try to resolve contained references across this boundary return null; // we don't try to resolve contained references across this boundary
} }
stack = stack.getParent(); stack = stack.getParent();
@ -2963,6 +2965,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return rr; return rr;
} }
} }
if (stack.getElement().getSpecial() == SpecialElement.PARAMETER && stack.getParent() != null) {
NodeStack tgt = findInParams(stack.getParent().getParent(), ref);
if (tgt != null) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(tgt.getElement());
rr.setFocus(tgt.getElement());
rr.setExternal(false);
rr.setStack(tgt);
rr.getStack().qualifyPath(".ofType("+tgt.getElement().fhirType()+")");
return rr;
}
}
stack = stack.getParent(); stack = stack.getParent();
} }
// we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity. // we can get here if we got called via FHIRPath conformsTo which breaks the stack continuity.
@ -2989,6 +3003,42 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return null; return null;
} }
private NodeStack findInParams(NodeStack params, String ref) {
int i = 0;
for (Element child : params.getElement().getChildren("parameter")) {
NodeStack p = params.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
if (child.hasChild("resource")) {
Element res = child.getNamedChild("resource");
if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
}
}
NodeStack pc = findInParamParts(p, child, ref);
if (pc != null) {
return pc;
}
}
return null;
}
private NodeStack findInParamParts(NodeStack pp, Element param, String ref) {
int i = 0;
for (Element child : param.getChildren("part")) {
NodeStack p = pp.push(child, i, child.getProperty().getDefinition(), child.getProperty().getDefinition());
if (child.hasChild("resource")) {
Element res = child.getNamedChild("resource");
if ((res.fhirType()+"/"+res.getIdBase()).equals(ref)) {
return p.push(res, -1, res.getProperty().getDefinition(), res.getProperty().getDefinition());
}
}
NodeStack pc = findInParamParts(p, child, ref);
if (pc != null) {
return pc;
}
}
return null;
}
private Element getEntryForSource(Element bundle, Element element) { private Element getEntryForSource(Element bundle, Element element) {
List<Element> entries = new ArrayList<Element>(); List<Element> entries = new ArrayList<Element>();
bundle.getNamedChildren(ENTRY, entries); bundle.getNamedChildren(ENTRY, entries);
@ -3432,7 +3482,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
for (Element profile : profiles) { for (Element profile : profiles) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue()); StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile.primitiveValue());
if (!defn.getUrl().equals(profile.primitiveValue())) { if (!defn.getUrl().equals(profile.primitiveValue())) {
if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) { // is this a version specific reference?
VersionURLInfo vu = VersionUtilities.parseVersionUrl(profile.primitiveValue());
if (vu != null) {
if (!VersionUtilities.versionsCompatible(vu.getVersion(), context.getVersion())) {
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_OTHER_VERSION, vu.getVersion());
} else if (vu.getUrl().equals(defn.getUrl())) {
hint(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OK);
} else {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, vu.getUrl());
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_THIS_VERSION_OTHER, sdt == null ? "null" : sdt.getType());
}
} else if (warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", sd != null, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN, profile.primitiveValue())) {
signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl()); signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), !crumbTrails, I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_META, sd.getUrl());
stack.resetIds(); stack.resetIds();
startInner(hostContext, errors, resource, element, sd, stack, false); startInner(hostContext, errors, resource, element, sd, stack, false);
@ -3658,9 +3719,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (trr == null) { if (trr == null) {
rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName); rule(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_TYPE, resourceName);
} else if (isValidResourceType(resourceName, trr)) { } else if (isValidResourceType(resourceName, trr)) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
timeTracker.sd(t);
// special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise // special case: resource wrapper is reset if we're crossing a bundle boundary, but not otherwise
ValidatorHostContext hc = null; ValidatorHostContext hc = null;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER) { if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY || element.getSpecial() == SpecialElement.BUNDLE_OUTCOME || element.getSpecial() == SpecialElement.PARAMETER) {
@ -3669,11 +3727,30 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else { } else {
hc = hostContext.forContained(element); hc = hostContext.forContained(element);
} }
trackUsage(profile, hostContext, element);
stack.resetIds(); stack.resetIds();
if (trr.getProfile().size() == 1) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, trr.getProfile().get(0).asStringValue());
timeTracker.sd(t);
trackUsage(profile, hostContext, element);
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) { if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) {
validateResource(hc, errors, resource, element, profile, idstatus, stack); validateResource(hc, errors, resource, element, profile, idstatus, stack);
} }
} else if (trr.getProfile().size() == 0) {
long t = System.nanoTime();
StructureDefinition profile = this.context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + resourceName);
timeTracker.sd(t);
trackUsage(profile, hostContext, element);
if (rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE, resourceName)) {
validateResource(hc, errors, resource, element, profile, idstatus, stack);
}
} else {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (CanonicalType u : trr.getProfile()) {
b.append(u.asStringValue());
}
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES, trr.getCode(), b.toString());
}
} else { } else {
List<String> types = new ArrayList<>(); List<String> types = new ArrayList<>();
for (UriType u : trr.getProfile()) { for (UriType u : trr.getProfile()) {