Refactoring validator engine (#436)

* Initial commit, tests passing

* continuing breaking up massive classes
This commit is contained in:
Mark Iantorno 2021-02-15 16:48:40 -05:00 committed by GitHub
parent ab5d8ee118
commit c422ddc388
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1840 additions and 1990 deletions

View File

@ -48,7 +48,6 @@ public class OIDBasedValueSetImporter {
return csver;
}
protected ConceptSetComponent getInclude(ValueSet vs, String url, String csver) {
for (ConceptSetComponent t : vs.getCompose().getInclude()) {
if (csver == null) {
@ -66,20 +65,4 @@ public class OIDBasedValueSetImporter {
c.setVersion(csver);
return c;
}
protected Document loadXml(InputStream fn) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(fn);
}
}

View File

@ -837,6 +837,5 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon
}
return xverManager;
}
}

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.r5.utils;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
@ -112,8 +112,8 @@ public interface IResourceValidator {
boolean resolveURL(Object appContext, String path, String url, String type) throws IOException, FHIRException;
byte[] fetchRaw(String url) throws MalformedURLException, IOException; // for attachment checking
void setLocale(Locale locale);
IValidatorResourceFetcher setLocale(Locale locale);
/**

View File

@ -146,6 +146,14 @@
<optional>true</optional>
</dependency>
<!-- Lombok Until I get Around to Rewriting Everything in Kotlin -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>

View File

@ -0,0 +1,661 @@
package org.hl7.fhir.validation;
import lombok.Getter;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.turtle.Turtle;
import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class IgLoader {
private static final String[] IGNORED_EXTENSIONS = {"md", "css", "js", "png", "gif", "jpg", "html", "tgz", "pack", "zip"};
private static final String[] EXEMPT_FILES = {"spec.internals", "version.info", "schematron.zip", "package.json"};
@Getter private final FilesystemPackageCacheManager packageCacheManager;
@Getter private final SimpleWorkerContext context;
@Getter private final String version;
@Getter private final boolean isDebug;
public IgLoader(FilesystemPackageCacheManager packageCacheManager,
SimpleWorkerContext context,
String theVersion) {
this(packageCacheManager, context, theVersion, false);
}
public IgLoader(FilesystemPackageCacheManager packageCacheManager,
SimpleWorkerContext context,
String theVersion,
boolean isDebug) {
this.packageCacheManager = packageCacheManager;
this.context = context;
this.version = theVersion;
this.isDebug = isDebug;
}
public void loadIg(List<ImplementationGuide> igs,
Map<String, byte[]> binaries,
String src,
boolean recursive) throws IOException, FHIRException {
NpmPackage npm = src.matches(FilesystemPackageCacheManager.PACKAGE_VERSION_REGEX_OPT) && !new File(src).exists() ? getPackageCacheManager().loadPackage(src, null) : null;
if (npm != null) {
for (String s : npm.dependencies()) {
if (!getContext().getLoadedPackages().contains(s)) {
if (!VersionUtilities.isCorePackage(s)) {
loadIg(igs, binaries, s, false);
}
}
}
System.out.print(" Load " + src);
if (!src.contains("#")) {
System.out.print("#" + npm.version());
}
int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion()));
System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")");
} else {
System.out.print(" Load " + src);
String canonical = null;
int count = 0;
Map<String, byte[]> source = loadIgSource(src, recursive, true);
String version = Constants.VERSION;
if (getVersion() != null) {
version = getVersion();
}
if (source.containsKey("version.info")) {
version = readInfoVersion(source.get("version.info"));
}
for (Map.Entry<String, byte[]> t : source.entrySet()) {
String fn = t.getKey();
if (!exemptFile(fn)) {
Resource r = loadFileWithErrorChecking(version, t, fn);
if (r != null) {
count++;
getContext().cacheResource(r);
if (r instanceof ImplementationGuide) {
canonical = ((ImplementationGuide) r).getUrl();
igs.add((ImplementationGuide) r);
if (canonical.contains("/ImplementationGuide/")) {
Resource r2 = r.copy();
((ImplementationGuide) r2).setUrl(canonical.substring(0, canonical.indexOf("/ImplementationGuide/")));
getContext().cacheResource(r2);
}
}
}
}
}
if (canonical != null) {
ValidatorUtils.grabNatives(binaries, source, canonical);
}
System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")");
}
}
public Content loadContent(String source, String opName, boolean asIg) throws FHIRException, IOException {
Map<String, byte[]> s = loadIgSource(source, false, asIg);
Content res = new Content();
if (s.size() != 1)
throw new FHIRException("Unable to find resource " + source + " to " + opName);
for (Map.Entry<String, byte[]> t : s.entrySet()) {
res.focus = t.getValue();
if (t.getKey().endsWith(".json"))
res.cntType = Manager.FhirFormat.JSON;
else if (t.getKey().endsWith(".xml"))
res.cntType = Manager.FhirFormat.XML;
else if (t.getKey().endsWith(".ttl"))
res.cntType = Manager.FhirFormat.TURTLE;
else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map"))
res.cntType = Manager.FhirFormat.TEXT;
else
throw new FHIRException("Todo: Determining resource type is not yet done");
}
return res;
}
/**
* explore should be true if we're trying to load an -ig parameter, and false if we're loading source
*
* @throws IOException
**/
public Map<String, byte[]> loadIgSource(String src,
boolean recursive,
boolean explore) throws FHIRException, IOException {
// 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 package id for an ig - this will be loaded into the cache
// - a direct reference to a package ("package.tgz") - this will be extracted by the cache manager, but not put in the cache
// - a folder containing resources - these will be loaded directly
if (Common.isNetworkPath(src)) {
String v = null;
if (src.contains("|")) {
v = src.substring(src.indexOf("|") + 1);
src = src.substring(0, src.indexOf("|"));
}
String pid = explore ? getPackageCacheManager().getPackageId(src) : null;
if (!Utilities.noString(pid))
return fetchByPackage(pid + (v == null ? "" : "#" + v));
else
return fetchFromUrl(src + (v == null ? "" : "|" + v), explore);
}
File f = new File(Utilities.path(src));
if (f.exists()) {
if (f.isDirectory() && new File(Utilities.path(src, "package.tgz")).exists())
return loadPackage(new FileInputStream(Utilities.path(src, "package.tgz")), Utilities.path(src, "package.tgz"));
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"))
return loadPackage(new FileInputStream(src), src);
if (src.endsWith(".pack"))
return readZip(new FileInputStream(src));
if (src.endsWith("igpack.zip"))
return readZip(new FileInputStream(src));
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), 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")) {
return fetchByPackage(src);
}
throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src);
}
public void scanForIgVersion(String src,
boolean recursive,
VersionSourceInformation versions) throws 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);
}
protected Map<String, byte[]> readZip(InputStream stream) throws IOException {
Map<String, byte[]> res = new HashMap<>();
ZipInputStream zip = new ZipInputStream(stream);
ZipEntry ze;
while ((ze = zip.getNextEntry()) != null) {
String name = ze.getName();
ByteArrayOutputStream b = new ByteArrayOutputStream();
int n;
byte[] buf = new byte[1024];
while ((n = ((InputStream) zip).read(buf, 0, 1024)) > -1) {
b.write(buf, 0, n);
}
res.put(name, b.toByteArray());
zip.closeEntry();
}
zip.close();
return res;
}
private String loadPackageForVersion(InputStream stream) throws FHIRException, IOException {
return NpmPackage.fromPackage(stream).fhirVersion();
}
private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException {
try {
URL url = new URL(source + "?nocache=" + System.currentTimeMillis());
URLConnection c = url.openConnection();
return c.getInputStream();
} catch (IOException e) {
if (optional)
return null;
else
throw e;
}
}
private Map<String, byte[]> loadIgSourceForVersion(String src,
boolean recursive,
boolean explore,
VersionSourceInformation versions) throws FHIRException, IOException {
if (Common.isNetworkPath(src)) {
String v = null;
if (src.contains("|")) {
v = src.substring(src.indexOf("|") + 1);
src = src.substring(0, src.indexOf("|"));
}
String pid = getPackageCacheManager().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"))), "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)), "Package " + src);
return null;
}
if (src.endsWith(".pack"))
return readZip(new FileInputStream(src));
if (src.endsWith("igpack.zip"))
return readZip(new FileInputStream(src));
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), 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[]> fetchByPackage(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 (version == null) {
version = getPackageCacheManager().getLatestVersion(id);
}
NpmPackage pi;
if (version == null) {
pi = getPackageCacheManager().loadPackageFromCacheOnly(id);
if (pi != null)
System.out.println(" ... Using version " + pi.version());
} else
pi = getPackageCacheManager().loadPackageFromCacheOnly(id, version);
if (pi == null) {
return resolvePackage(id, version);
} else
return loadPackage(pi);
}
private Map<String, byte[]> loadPackage(InputStream stream, String name) throws FHIRException, IOException {
return loadPackage(NpmPackage.fromPackage(stream));
}
public Map<String, byte[]> loadPackage(NpmPackage pi) throws FHIRException, IOException {
getContext().getLoadedPackages().add(pi.name() + "#" + pi.version());
Map<String, byte[]> res = new HashMap<String, byte[]>();
for (String s : pi.dependencies()) {
if (s.endsWith(".x") && s.length() > 2) {
String packageMajorMinor = s.substring(0, s.length() - 2);
boolean found = false;
for (int i = 0; i < getContext().getLoadedPackages().size() && !found; ++i) {
String loadedPackage = getContext().getLoadedPackages().get(i);
if (loadedPackage.startsWith(packageMajorMinor)) {
found = true;
}
}
if (found)
continue;
}
if (!getContext().getLoadedPackages().contains(s)) {
if (!VersionUtilities.isCorePackage(s)) {
System.out.println("+ .. load IG from " + s);
res.putAll(fetchByPackage(s));
}
}
}
for (String s : pi.listResources("CodeSystem", "ConceptMap", "ImplementationGuide", "CapabilityStatement", "SearchParameter", "Conformance", "StructureMap", "ValueSet", "StructureDefinition")) {
res.put(s, TextFile.streamToBytes(pi.load("package", s)));
}
String ini = "[FHIR]\r\nversion=" + pi.fhirVersion() + "\r\n";
res.put("version.info", ini.getBytes());
return res;
}
private Map<String, byte[]> resolvePackage(String id, String v) throws FHIRException, IOException {
NpmPackage pi = getPackageCacheManager().loadPackage(id, v);
if (pi != null && v == null)
System.out.println(" ... Using version " + pi.version());
return loadPackage(pi);
}
private String readInfoVersion(byte[] bs) throws IOException {
String is = TextFile.bytesToString(bs);
is = is.trim();
IniFile ini = new IniFile(new ByteArrayInputStream(TextFile.stringToBytes(is, false)));
return ini.getStringProperty("FHIR", "version");
}
private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional, List<String> errors) throws FHIRException, IOException {
try {
try {
// try with cache-busting option and then try withhout in case the server doesn't support that
URL url = new URL(source + "?nocache=" + System.currentTimeMillis());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept", contentType);
return TextFile.streamToBytes(conn.getInputStream());
} catch (Exception e) {
URL url = new URL(source);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("Accept", contentType);
return TextFile.streamToBytes(conn.getInputStream());
}
} catch (IOException e) {
if (errors != null) {
errors.add("Error accessing " + source + ": " + e.getMessage());
}
if (optional)
return null;
else
throw e;
}
}
private Map<String, byte[]> fetchVersionFromUrl(String src,
boolean explore,
VersionSourceInformation versions) throws FHIRException, IOException {
if (src.endsWith(".tgz")) {
versions.see(loadPackageForVersion(fetchFromUrlSpecific(src, false)), "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), "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, null);
else
cnt = TextFile.streamToBytes(stream);
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt);
return res;
}
String fn = Utilities.path("[tmp]", "fetch-resource-error-content.bin");
TextFile.bytesToFile(cnt, fn);
System.out.println("Error Fetching " + src);
System.out.println("Some content was found, saved to " + fn);
System.out.println("1st 100 bytes = " + presentForDebugging(cnt));
throw new FHIRException("Unable to find/resolve/read " + (explore ? "-ig " : "") + src);
}
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 (version == null) {
version = getPackageCacheManager().getLatestVersion(id);
}
NpmPackage pi = null;
if (version == null) {
pi = getPackageCacheManager().loadPackageFromCacheOnly(id);
if (pi != null)
System.out.println(" ... Using version " + pi.version());
} else
pi = getPackageCacheManager().loadPackageFromCacheOnly(id, version);
if (pi == null) {
return resolvePackageForVersion(id, version);
} else {
return pi.fhirVersion();
}
}
private Map<String, byte[]> fetchFromUrl(String src, boolean explore) throws FHIRException, IOException {
if (src.endsWith(".tgz"))
return loadPackage(fetchFromUrlSpecific(src, false), src);
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)
return loadPackage(stream, Utilities.pathURL(src, "package.tgz"));
// 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;
List<String> errors = new ArrayList<>();
if (stream != null) {
cnt = TextFile.streamToBytes(stream);
} else {
cnt = fetchFromUrlSpecific(src, "application/json", true, errors);
if (cnt == null) {
cnt = fetchFromUrlSpecific(src, "application/xml", true, errors);
}
}
if (cnt == null) {
throw new FHIRException("Unable to fetch content from " + src + " (" + errors.toString() + ")");
}
Manager.FhirFormat fmt = checkFormat(cnt, src);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<>();
res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt);
return res;
}
throw new FHIRException("Unable to read content from " + src + ": cannot determine format");
}
private boolean isIgnoreFile(File ff) {
if (ff.getName().startsWith(".") || ff.getAbsolutePath().contains(".git")) {
return true;
}
return Utilities.existsInList(Utilities.getFileExtension(ff.getName()).toLowerCase(), IGNORED_EXTENSIONS);
}
private Map<String, byte[]> scanDirectory(File f, boolean recursive) throws IOException {
Map<String, byte[]> res = new HashMap<>();
for (File ff : f.listFiles()) {
if (ff.isDirectory() && recursive) {
res.putAll(scanDirectory(ff, true));
} else if (!isIgnoreFile(ff)) {
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), ff.getAbsolutePath());
if (fmt != null) {
res.put(Utilities.changeFileExt(ff.getName(), "." + fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath()));
}
}
}
return res;
}
private String resolvePackageForVersion(String id, String v) throws FHIRException, IOException {
NpmPackage pi = getPackageCacheManager().loadPackage(id, v);
return pi.fhirVersion();
}
private String presentForDebugging(byte[] cnt) {
StringBuilder b = new StringBuilder();
for (int i = 0; i < Integer.min(cnt.length, 50); i++) {
b.append(Integer.toHexString(cnt[i]));
}
return b.toString();
}
private Manager.FhirFormat checkFormat(byte[] cnt, String filename) {
System.out.println(" ..Detect format for " + filename);
try {
JsonTrackingParser.parseJson(cnt);
return Manager.FhirFormat.JSON;
} catch (Exception e) {
log("Not JSON: " + e.getMessage());
}
try {
ValidatorUtils.parseXml(cnt);
return Manager.FhirFormat.XML;
} catch (Exception e) {
log("Not XML: " + e.getMessage());
}
try {
new Turtle().parse(TextFile.bytesToString(cnt));
return Manager.FhirFormat.TURTLE;
} catch (Exception e) {
log("Not Turtle: " + e.getMessage());
}
try {
new StructureMapUtilities(getContext(), null, null).parse(TextFile.bytesToString(cnt), null);
return Manager.FhirFormat.TEXT;
} catch (Exception e) {
log("Not Text: " + e.getMessage());
}
log(" .. not a resource: " + filename);
return null;
}
private boolean exemptFile(String fn) {
return Utilities.existsInList(fn, EXEMPT_FILES);
}
private Resource loadFileWithErrorChecking(String version, Map.Entry<String, byte[]> t, String fn) {
log("* load file: " + fn);
Resource r = null;
try {
r = loadResourceByVersion(version, t.getValue(), fn);
log(" .. success");
} catch (Exception e) {
if (!isDebug()) {
System.out.print("* load file: " + fn);
}
System.out.println(" - ignored due to error: " + (e.getMessage() == null ? " (null - NPE)" : e.getMessage()));
if (isDebug() || ((e.getMessage() != null && e.getMessage().contains("cannot be cast")))) {
e.printStackTrace();
}
}
return r;
}
public Resource loadResourceByVersion(String fhirVersion, byte[] content, String fn) throws IOException, FHIRException {
Resource r;
if (fhirVersion.startsWith("3.0")) {
org.hl7.fhir.dstu3.model.Resource res;
if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
res = new org.hl7.fhir.dstu3.formats.XmlParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".txt") || fn.endsWith(".map"))
res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content));
else
throw new FHIRException("Unsupported format for " + fn);
r = VersionConvertor_30_50.convertResource(res, false);
} else if (fhirVersion.startsWith("4.0")) {
org.hl7.fhir.r4.model.Resource res;
if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
res = new org.hl7.fhir.r4.formats.XmlParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.r4.formats.JsonParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".txt") || fn.endsWith(".map"))
res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn);
else
throw new FHIRException("Unsupported format for " + fn);
r = VersionConvertor_40_50.convertResource(res);
} else if (fhirVersion.startsWith("1.4")) {
org.hl7.fhir.dstu2016may.model.Resource res;
if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
res = new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new ByteArrayInputStream(content));
else
throw new FHIRException("Unsupported format for " + fn);
r = VersionConvertor_14_50.convertResource(res);
} else if (fhirVersion.startsWith("1.0")) {
org.hl7.fhir.dstu2.model.Resource res;
if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
res = new org.hl7.fhir.dstu2.formats.JsonParser().parse(new ByteArrayInputStream(content));
else
throw new FHIRException("Unsupported format for " + fn);
VersionConvertorAdvisor50 advisor = new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5();
r = VersionConvertor_10_50.convertResource(res, advisor);
} else if (fhirVersion.equals(Constants.VERSION) || "current".equals(fhirVersion)) {
if (fn.endsWith(".xml") && !fn.endsWith("template.xml"))
r = new XmlParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
r = new JsonParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".txt"))
r = new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(content), fn);
else if (fn.endsWith(".map"))
r = new StructureMapUtilities(null).parse(new String(content), fn);
else
throw new FHIRException("Unsupported format for " + fn);
} else
throw new FHIRException("Unsupported version " + fhirVersion);
return r;
}
private void log(String s) {
if (isDebug()) System.out.println(s);
}
}

View File

@ -1,33 +1,33 @@
package org.hl7.fhir.validation;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
@ -141,6 +141,7 @@ public class NativeHostServices {
}
private ValidationEngine validator;
private IgLoader igLoader;
private int validationCount = 0;
private int resourceCount = 0;
private int convertCount = 0;
@ -167,6 +168,7 @@ public class NativeHostServices {
public void init(String pack) throws Exception {
validator = new ValidationEngine(pack);
validator.getContext().setAllowLoadingDuplicates(true);
igLoader = new IgLoader(validator.getPcm(), validator.getContext(), validator.getVersion(), validator.isDebug());
}
/**
@ -177,7 +179,7 @@ public class NativeHostServices {
* @throws Exception
*/
public void load(String pack) throws Exception {
validator.loadIg(pack, false);
igLoader.loadIg(validator.getIgs(), validator.getBinaries(), pack, false);
}
/**

View File

@ -0,0 +1,67 @@
package org.hl7.fhir.validation;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class ResourceChecker {
protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, byte[] cnt, String filename) {
System.out.println(" ..Detect format for " + filename);
try {
Manager.parse(context, new ByteArrayInputStream(cnt), Manager.FhirFormat.JSON);
return Manager.FhirFormat.JSON;
} catch (Exception e) {
if (debug) {
System.out.println("Not JSON: " + e.getMessage());
}
}
try {
ValidatorUtils.parseXml(cnt);
return Manager.FhirFormat.XML;
} catch (Exception e) {
if (debug) {
System.out.println("Not XML: " + e.getMessage());
}
}
try {
Manager.parse(context, new ByteArrayInputStream(cnt), Manager.FhirFormat.TURTLE);
return Manager.FhirFormat.TURTLE;
} catch (Exception e) {
if (debug) {
System.out.println("Not Turtle: " + e.getMessage());
}
}
try {
new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(cnt), null);
return Manager.FhirFormat.TEXT;
} catch (Exception e) {
if (debug) {
System.out.println("Not Text: " + e.getMessage());
}
}
if (debug)
System.out.println(" .. not a resource: " + filename);
return null;
}
protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, String path) throws IOException {
String ext = Utilities.getFileExtension(path);
if (Utilities.existsInList(ext, "xml"))
return Manager.FhirFormat.XML;
if (Utilities.existsInList(ext, "json"))
return Manager.FhirFormat.JSON;
if (Utilities.existsInList(ext, "ttl"))
return Manager.FhirFormat.TURTLE;
if (Utilities.existsInList(ext, "map"))
return Manager.FhirFormat.TEXT;
if (Utilities.existsInList(ext, "txt"))
return Manager.FhirFormat.TEXT;
return checkIsResource(context, debug, TextFile.fileToBytes(path), path);
}
}

View File

@ -0,0 +1,362 @@
package org.hl7.fhir.validation;
import lombok.Getter;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.validation.cli.model.ScanOutputItem;
import org.hl7.fhir.validation.instance.InstanceValidator;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class Scanner {
private static final int BUFFER_SIZE = 4096;
@Getter private final SimpleWorkerContext context;
@Getter private final InstanceValidator validator;
@Getter private final IgLoader igLoader;
@Getter private final FHIRPathEngine fhirPathEngine;
public Scanner(SimpleWorkerContext context, InstanceValidator validator, IgLoader igLoader, FHIRPathEngine fhirPathEngine) {
this.context = context;
this.validator = validator;
this.igLoader = igLoader;
this.fhirPathEngine = fhirPathEngine;
}
public void validateScan(String output, List<String> sources) throws Exception {
if (Utilities.noString(output))
throw new Exception("Output parameter required when scanning");
if (!(new File(output).isDirectory()))
throw new Exception("Output '" + output + "' must be a directory when scanning");
System.out.println(" .. scan " + sources + " against loaded IGs");
Set<String> urls = new HashSet<>();
for (ImplementationGuide ig : getContext().allImplementationGuides()) {
if (ig.getUrl().contains("/ImplementationGuide") && !ig.getUrl().equals("http://hl7.org/fhir/ImplementationGuide/fhir"))
urls.add(ig.getUrl());
}
List<ScanOutputItem> res = validateScan(sources, urls);
genScanOutput(output, res);
System.out.println("Done. output in " + Utilities.path(output, "scan.html"));
}
protected List<ScanOutputItem> validateScan(List<String> sources, Set<String> guides) throws FHIRException, IOException, EOperationOutcome {
List<String> refs = new ArrayList<>();
ValidatorUtils.parseSources(sources, refs, getContext());
List<ScanOutputItem> res = new ArrayList();
for (String ref : refs) {
Content cnt = getIgLoader().loadContent(ref, "validate", false);
List<ValidationMessage> messages = new ArrayList<>();
Element e = null;
try {
System.out.println("Validate " + ref);
messages.clear();
e = getValidator().validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType);
res.add(new ScanOutputItem(ref, null, null, ValidatorUtils.messagesToOutcome(messages, getContext(), getFhirPathEngine())));
} catch (Exception ex) {
res.add(new ScanOutputItem(ref, null, null, exceptionToOutcome(ex)));
}
if (e != null) {
String rt = e.fhirType();
for (String u : guides) {
ImplementationGuide ig = getContext().fetchResource(ImplementationGuide.class, u);
System.out.println("Check Guide " + ig.getUrl());
String canonical = ig.getUrl().contains("/Impl") ? ig.getUrl().substring(0, ig.getUrl().indexOf("/Impl")) : ig.getUrl();
String url = getGlobal(ig, rt);
if (url != null) {
try {
System.out.println("Validate " + ref + " against " + ig.getUrl());
messages.clear();
getValidator().validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType, url);
res.add(new ScanOutputItem(ref, ig, null, ValidatorUtils.messagesToOutcome(messages, getContext(), getFhirPathEngine())));
} catch (Exception ex) {
res.add(new ScanOutputItem(ref, ig, null, exceptionToOutcome(ex)));
}
}
Set<String> done = new HashSet<>();
for (StructureDefinition sd : getContext().allStructures()) {
if (!done.contains(sd.getUrl())) {
done.add(sd.getUrl());
if (sd.getUrl().startsWith(canonical) && rt.equals(sd.getType())) {
try {
System.out.println("Validate " + ref + " against " + sd.getUrl());
messages.clear();
validator.validate(null, messages, new ByteArrayInputStream(cnt.focus), cnt.cntType, Collections.singletonList(sd));
res.add(new ScanOutputItem(ref, ig, sd, ValidatorUtils.messagesToOutcome(messages, getContext(), getFhirPathEngine())));
} catch (Exception ex) {
res.add(new ScanOutputItem(ref, ig, sd, exceptionToOutcome(ex)));
}
}
}
}
}
}
}
return res;
}
protected void genScanOutput(String folder, List<ScanOutputItem> items) throws IOException, FHIRException, EOperationOutcome {
String f = Utilities.path(folder, "comparison.zip");
download("http://fhir.org/archive/comparison.zip", f);
unzip(f, folder);
for (int i = 0; i < items.size(); i++) {
items.get(i).setId("c" + i);
genScanOutputItem(items.get(i), Utilities.path(folder, items.get(i).getId() + ".html"));
}
StringBuilder b = new StringBuilder();
b.append("<html>");
b.append("<head>");
b.append("<title>Implementation Guide Scan</title>");
b.append("<link rel=\"stylesheet\" href=\"fhir.css\"/>\r\n");
b.append("<style>\r\n");
b.append("th \r\n");
b.append("{\r\n");
b.append(" vertical-align: bottom;\r\n");
b.append(" text-align: center;\r\n");
b.append("}\r\n");
b.append("\r\n");
b.append("th span\r\n");
b.append("{\r\n");
b.append(" -ms-writing-mode: tb-rl;\r\n");
b.append(" -webkit-writing-mode: vertical-rl;\r\n");
b.append(" writing-mode: vertical-rl;\r\n");
b.append(" transform: rotate(180deg);\r\n");
b.append(" white-space: nowrap;\r\n");
b.append("}\r\n");
b.append("</style>\r\n");
b.append("</head>");
b.append("<body>");
b.append("<h2>Implementation Guide Scan</h2>");
// organise
Set<String> refs = new HashSet<>();
Set<String> igs = new HashSet<>();
Map<String, Set<String>> profiles = new HashMap<>();
for (ScanOutputItem item : items) {
refs.add(item.getRef());
if (item.getIg() != null) {
igs.add(item.getIg().getUrl());
if (!profiles.containsKey(item.getIg().getUrl())) {
profiles.put(item.getIg().getUrl(), new HashSet<>());
}
if (item.getProfile() != null)
profiles.get(item.getIg().getUrl()).add(item.getProfile().getUrl());
}
}
b.append("<h2>By reference</h2>\r\n");
b.append("<table class=\"grid\">");
b.append("<tr><th></th><th></th>");
for (String s : sort(igs)) {
ImplementationGuide ig = getContext().fetchResource(ImplementationGuide.class, s);
b.append("<th colspan=\"" + Integer.toString(profiles.get(s).size() + 1) + "\"><b title=\"" + s + "\">" + ig.present() + "</b></th>");
}
b.append("</tr>\r\n");
b.append("<tr><th><b>Source</b></th><th><span>Core Spec</span></th>");
for (String s : sort(igs)) {
ImplementationGuide ig = getContext().fetchResource(ImplementationGuide.class, s);
b.append("<th><span>Global</span></th>");
for (String sp : sort(profiles.get(s))) {
StructureDefinition sd = getContext().fetchResource(StructureDefinition.class, sp);
b.append("<th><b title=\"" + sp + "\"><span>" + sd.present() + "</span></b></th>");
}
}
b.append("</tr>\r\n");
for (String s : sort(refs)) {
b.append("<tr>");
b.append("<td>" + s + "</td>");
b.append(genOutcome(items, s, null, null));
for (String si : sort(igs)) {
ImplementationGuide ig = getContext().fetchResource(ImplementationGuide.class, si);
b.append(genOutcome(items, s, si, null));
for (String sp : sort(profiles.get(ig.getUrl()))) {
b.append(genOutcome(items, s, si, sp));
}
}
b.append("</tr>\r\n");
}
b.append("</table>\r\n");
b.append("<h2>By IG</h2>\r\n");
b.append("<table class=\"grid\">");
b.append("<tr><th></th><th></th>");
for (String s : sort(refs)) {
b.append("<th><span>" + s + "</span></th>");
}
b.append("</tr>\r\n");
b.append("<tr><td></td><td>Core Spec</td>");
for (String s : sort(refs)) {
b.append(genOutcome(items, s, null, null));
}
b.append("</tr>\r\n");
for (String si : sort(igs)) {
b.append("<tr>");
ImplementationGuide ig = getContext().fetchResource(ImplementationGuide.class, si);
b.append("<td><b title=\"" + si + "\">" + ig.present() + "</b></td>");
b.append("<td>Global</td>");
for (String s : sort(refs)) {
b.append(genOutcome(items, s, si, null));
}
b.append("</tr>\r\n");
for (String sp : sort(profiles.get(ig.getUrl()))) {
b.append("<tr>");
StructureDefinition sd = getContext().fetchResource(StructureDefinition.class, sp);
b.append("<td></td><td><b title=\"" + sp + "\">" + sd.present() + "</b></td>");
for (String s : sort(refs)) {
b.append(genOutcome(items, s, si, sp));
}
b.append("</tr>\r\n");
}
}
b.append("</table>\r\n");
b.append("</body>");
b.append("</html>");
TextFile.stringToFile(b.toString(), Utilities.path(folder, "scan.html"));
}
protected void genScanOutputItem(ScanOutputItem item, String filename) throws IOException, FHIRException, EOperationOutcome {
RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE);
rc.setNoSlowLookup(true);
RendererFactory.factory(item.getOutcome(), rc).render(item.getOutcome());
String s = new XhtmlComposer(XhtmlComposer.HTML).compose(item.getOutcome().getText().getDiv());
String title = item.getTitle();
StringBuilder b = new StringBuilder();
b.append("<html>");
b.append("<head>");
b.append("<title>" + title + "</title>");
b.append("<link rel=\"stylesheet\" href=\"fhir.css\"/>\r\n");
b.append("</head>");
b.append("<body>");
b.append("<h2>" + title + "</h2>");
b.append(s);
b.append("</body>");
b.append("</html>");
TextFile.stringToFile(b.toString(), filename);
}
protected String genOutcome(List<ScanOutputItem> items, String src, String ig, String profile) {
ScanOutputItem item = null;
for (ScanOutputItem t : items) {
boolean match = true;
if (!t.getRef().equals(src))
match = false;
if (!((ig == null && t.getIg() == null) || (ig != null && t.getIg() != null && ig.equals(t.getIg().getUrl()))))
match = false;
if (!((profile == null && t.getProfile() == null) || (profile != null && t.getProfile() != null && profile.equals(t.getProfile().getUrl()))))
match = false;
if (match) {
item = t;
break;
}
}
if (item == null)
return "<td></td>";
boolean ok = true;
for (OperationOutcome.OperationOutcomeIssueComponent iss : item.getOutcome().getIssue()) {
if (iss.getSeverity() == OperationOutcome.IssueSeverity.ERROR || iss.getSeverity() == OperationOutcome.IssueSeverity.FATAL) {
ok = false;
}
}
if (ok)
return "<td style=\"background-color: #e6ffe6\"><a href=\"" + item.getId() + ".html\">\u2714</a></td>";
else
return "<td style=\"background-color: #ffe6e6\"><a href=\"" + item.getId() + ".html\">\u2716</a></td>";
}
protected OperationOutcome exceptionToOutcome(Exception ex) throws IOException, FHIRException, EOperationOutcome {
OperationOutcome op = new OperationOutcome();
op.addIssue().setCode(OperationOutcome.IssueType.EXCEPTION).setSeverity(OperationOutcome.IssueSeverity.FATAL).getDetails().setText(ex.getMessage());
RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE);
RendererFactory.factory(op, rc).render(op);
return op;
}
protected void download(String address, String filename) throws IOException {
URL url = new URL(address);
URLConnection c = url.openConnection();
InputStream s = c.getInputStream();
FileOutputStream f = new FileOutputStream(filename);
transfer(s, f, 1024);
f.close();
}
protected void transfer(InputStream in, OutputStream out, int buffer) throws IOException {
byte[] read = new byte[buffer]; // Your buffer size.
while (0 < (buffer = in.read(read)))
out.write(read, 0, buffer);
}
protected List<String> sort(Set<String> keys) {
return keys.stream().sorted().collect(Collectors.toList());
}
protected void unzip(String zipFilePath, String destDirectory) throws IOException {
File destDir = new File(destDirectory);
if (!destDir.exists()) {
destDir.mkdir();
}
ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry entry = zipIn.getNextEntry();
// iterates over entries in the zip file
while (entry != null) {
String filePath = destDirectory + File.separator + entry.getName();
if (!entry.isDirectory()) {
// if the entry is a file, extracts it
extractFile(zipIn, filePath);
} else {
// if the entry is a directory, make the directory
File dir = new File(filePath);
dir.mkdir();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
zipIn.close();
}
protected void extractFile(ZipInputStream zipIn, String filePath) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
byte[] bytesIn = new byte[BUFFER_SIZE];
int read;
while ((read = zipIn.read(bytesIn)) != -1) {
bos.write(bytesIn, 0, read);
}
bos.close();
}
protected String getGlobal(ImplementationGuide ig, String rt) {
for (ImplementationGuide.ImplementationGuideGlobalComponent igg : ig.getGlobal()) {
if (rt.equals(igg.getType()))
return igg.getProfile();
}
return null;
}
}

View File

@ -0,0 +1,64 @@
package org.hl7.fhir.validation;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.terminologies.ConceptMapEngine;
import org.hl7.fhir.r5.utils.structuremap.ITransformerServices;
import java.io.PrintWriter;
import java.util.List;
public class TransformSupportServices implements ITransformerServices {
private final PrintWriter mapLog;
private final SimpleWorkerContext context;
private List<Base> outputs;
public TransformSupportServices(List<Base> outputs,
PrintWriter mapLog,
SimpleWorkerContext context) {
this.outputs = outputs;
this.mapLog = mapLog;
this.context = context;
}
@Override
public void log(String message) {
if (mapLog != null)
mapLog.println(message);
System.out.println(message);
}
@Override
public Base createType(Object appInfo, String name) throws FHIRException {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, name);
return Manager.build(context, sd);
}
@Override
public Base createResource(Object appInfo, Base res, boolean atRootofTransform) {
if (atRootofTransform)
outputs.add(res);
return res;
}
@Override
public Coding translate(Object appInfo, Coding source, String conceptMapUrl) throws FHIRException {
ConceptMapEngine cme = new ConceptMapEngine(context);
return cme.translate(source, conceptMapUrl);
}
@Override
public Base resolveReference(Object appContext, String url) throws FHIRException {
throw new FHIRException("resolveReference is not supported yet");
}
@Override
public List<Base> performSearch(Object appContext, String url) throws FHIRException {
throw new FHIRException("performSearch is not supported yet");
}
}

View File

@ -70,9 +70,6 @@ import org.hl7.fhir.validation.cli.utils.*;
import java.io.File;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.net.URLConnection;
import java.util.Base64;
/**
* A executable class that will validate one or more FHIR resources against
@ -232,7 +229,8 @@ public class ValidatorCli {
}
System.out.println("Validating");
if (cliContext.getMode() == EngineMode.SCAN) {
ValidationService.validateScan(cliContext, validator);
Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(), validator.getIgLoader(), validator.getFhirPathEngine());
validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
} else {
ValidationService.validateSources(cliContext, validator);
}

View File

@ -0,0 +1,149 @@
package org.hl7.fhir.validation;
import org.hl7.fhir.convertors.loaders.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.OperationOutcomeUtilities;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.cli.utils.AsteriskFilter;
import org.hl7.fhir.validation.cli.utils.Common;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
//TODO find a home for these and clean it up
public class ValidatorUtils {
protected static void grabNatives(Map<String, byte[]> source, Map<String, byte[]> binaries, String prefix) {
for (Map.Entry<String, byte[]> e : source.entrySet()) {
if (e.getKey().endsWith(".zip"))
binaries.put(prefix + "#" + e.getKey(), e.getValue());
}
}
protected static IWorkerContext.IContextResourceLoader loaderForVersion(String version) {
if (Utilities.noString(version))
return null;
if (version.startsWith("1.0"))
return new R2ToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new BaseLoaderR5.NullLoaderKnowledgeProvider());
if (version.startsWith("1.4"))
return new R2016MayToR5Loader(new String[]{"Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new BaseLoaderR5.NullLoaderKnowledgeProvider()); // special case
if (version.startsWith("3.0"))
return new R3ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new BaseLoaderR5.NullLoaderKnowledgeProvider());
if (version.startsWith("4.0"))
return new R4ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new BaseLoaderR5.NullLoaderKnowledgeProvider());
if (version.startsWith("5.0"))
return new R5ToR5Loader(new String[]{"CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem"}, new BaseLoaderR5.NullLoaderKnowledgeProvider());
return null;
}
protected static Document parseXml(byte[] cnt) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// xxe protection
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(cnt));
}
protected static List<ValidationMessage> filterMessages(List<ValidationMessage> messages) {
List<ValidationMessage> filteredValidation = new ArrayList<ValidationMessage>();
for (ValidationMessage e : messages) {
if (!filteredValidation.contains(e))
filteredValidation.add(e);
}
filteredValidation.sort(null);
return filteredValidation;
}
protected static OperationOutcome messagesToOutcome(List<ValidationMessage> messages, SimpleWorkerContext context, FHIRPathEngine fpe) throws IOException, FHIRException, EOperationOutcome {
OperationOutcome op = new OperationOutcome();
for (ValidationMessage vm : filterMessages(messages)) {
try {
fpe.parse(vm.getLocation());
} catch (Exception e) {
System.out.println("Internal error in location for message: '" + e.getMessage() + "', loc = '" + vm.getLocation() + "', err = '" + vm.getMessage() + "'");
}
op.getIssue().add(OperationOutcomeUtilities.convertToIssue(vm, op));
}
if (!op.hasIssue()) {
op.addIssue().setSeverity(OperationOutcome.IssueSeverity.INFORMATION).setCode(OperationOutcome.IssueType.INFORMATIONAL).getDetails().setText(context.formatMessage(I18nConstants.ALL_OK));
}
RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE);
RendererFactory.factory(op, rc).render(op);
return op;
}
/**
* Parses passed in resource path, adding any found references to the passed in list.
*
* @return {@link Boolean#TRUE} if more than one reference is found.
*/
static boolean extractReferences(String name, List<String> refs, SimpleWorkerContext context) throws IOException {
if (Common.isNetworkPath(name)) {
refs.add(name);
} else if (Common.isWildcardPath(name)) {
AsteriskFilter filter = new AsteriskFilter(name);
File[] files = new File(filter.getDir()).listFiles(filter);
for (File file : files) {
refs.add(file.getPath());
}
} else {
File file = new File(name);
if (!file.exists()) {
if (System.console() != null) {
System.console().printf(context.formatMessage(I18nConstants.BAD_FILE_PATH_ERROR, name));
} else {
System.out.println(context.formatMessage(I18nConstants.BAD_FILE_PATH_ERROR, name));
}
throw new IOException("File " + name + " does not exist");
}
if (file.isFile()) {
refs.add(name);
} else {
for (int i = 0; i < file.listFiles().length; i++) {
File[] fileList = file.listFiles();
if (fileList[i].isFile())
refs.add(fileList[i].getPath());
}
}
}
return refs.size() > 1;
}
/**
* Iterates through the list of passed in sources, extracting all references and populated them in the passed in list.
*
* @return {@link Boolean#TRUE} if more than one reference is found.
*/
public static boolean parseSources(List<String> sources, List<String> refs, SimpleWorkerContext context) throws IOException {
boolean multipleRefsFound = sources.size() > 1;
for (String source : sources) {
multipleRefsFound |= extractReferences(source, refs, context);
}
return multipleRefsFound;
}
}

View File

@ -0,0 +1,173 @@
package org.hl7.fhir.validation;
import org.hl7.fhir.convertors.*;
import org.hl7.fhir.dstu2016may.model.Resource;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.utilities.VersionUtilities;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class VersionConvertor {
public static byte[] convertVersionNativeR2(String targetVer, Content cnt, Manager.FhirFormat format) throws IOException, Exception {
org.hl7.fhir.dstu2.model.Resource r2;
switch (cnt.cntType) {
case JSON:
r2 = new org.hl7.fhir.dstu2.formats.JsonParser().parse(cnt.focus);
break;
case XML:
r2 = new org.hl7.fhir.dstu2.formats.XmlParser().parse(cnt.focus);
break;
default:
throw new FHIRException("Unsupported input format: " + cnt.cntType.toString());
}
if (VersionUtilities.isR2Ver(targetVer)) {
return getBytesDstu2(cnt, format, r2);
} else if (VersionUtilities.isR2BVer(targetVer)) {
org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_10_30.convertResource(r2);
org.hl7.fhir.dstu2016may.model.Resource r2b = VersionConvertor_14_30.convertResource(r3);
return getBytesDstu2016(cnt, format, r2b);
} else if (VersionUtilities.isR3Ver(targetVer)) {
return getBytesDstu3(cnt, format, VersionConvertor_10_30.convertResource(r2));
} else if (VersionUtilities.isR4Ver(targetVer)) {
return getBytesR4(cnt, format, VersionConvertor_10_40.convertResource(r2));
} else {
throw new FHIRException("Target Version not supported yet: " + targetVer);
}
}
public static byte[] convertVersionNativeR2b(String targetVer, Content cnt, Manager.FhirFormat format) throws IOException, Exception {
org.hl7.fhir.dstu2016may.model.Resource r2b;
switch (cnt.cntType) {
case JSON:
r2b = new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(cnt.focus);
break;
case XML:
r2b = new org.hl7.fhir.dstu2016may.formats.XmlParser().parse(cnt.focus);
break;
default:
throw new FHIRException("Unsupported input format: " + cnt.cntType.toString());
}
if (VersionUtilities.isR2Ver(targetVer)) {
org.hl7.fhir.dstu3.model.Resource r3 = VersionConvertor_14_30.convertResource(r2b);
org.hl7.fhir.dstu2.model.Resource r2 = VersionConvertor_10_30.convertResource(r3);
return getBytesDstu2(cnt, format, r2);
} else if (VersionUtilities.isR2BVer(targetVer)) {
return getBytesDstu2016(cnt, format, r2b);
} else if (VersionUtilities.isR3Ver(targetVer)) {
return getBytesDstu3(cnt, format, VersionConvertor_14_30.convertResource(r2b));
} else if (VersionUtilities.isR4Ver(targetVer)) {
return getBytesR4(cnt, format, VersionConvertor_14_40.convertResource(r2b));
} else {
throw new FHIRException("Target Version not supported yet: " + targetVer);
}
}
public static byte[] convertVersionNativeR3(String targetVer, Content cnt, Manager.FhirFormat format) throws IOException, Exception {
org.hl7.fhir.dstu3.model.Resource r3;
switch (cnt.cntType) {
case JSON:
r3 = new org.hl7.fhir.dstu3.formats.JsonParser().parse(cnt.focus);
break;
case XML:
r3 = new org.hl7.fhir.dstu3.formats.XmlParser().parse(cnt.focus);
break;
default:
throw new FHIRException("Unsupported input format: " + cnt.cntType.toString());
}
if (VersionUtilities.isR2Ver(targetVer)) {
return getBytesDstu2(cnt, format, VersionConvertor_10_30.convertResource(r3));
} else if (VersionUtilities.isR2BVer(targetVer)) {
return getBytesDstu2016(cnt, format, VersionConvertor_14_30.convertResource(r3));
} else if (VersionUtilities.isR3Ver(targetVer)) {
return getBytesDstu3(cnt, format, r3);
} else if (VersionUtilities.isR4Ver(targetVer)) {
return getBytesR4(cnt, format, VersionConvertor_30_40.convertResource(r3, false));
} else {
throw new FHIRException("Target Version not supported yet: " + targetVer);
}
}
public static byte[] convertVersionNativeR4(String targetVer, Content cnt, Manager.FhirFormat format) throws IOException, Exception {
org.hl7.fhir.r4.model.Resource r4;
switch (cnt.cntType) {
case JSON:
r4 = new org.hl7.fhir.r4.formats.JsonParser().parse(cnt.focus);
break;
case XML:
r4 = new org.hl7.fhir.r4.formats.XmlParser().parse(cnt.focus);
break;
default:
throw new FHIRException("Unsupported input format: " + cnt.cntType.toString());
}
if (VersionUtilities.isR2Ver(targetVer)) {
return getBytesDstu2(cnt, format, VersionConvertor_10_40.convertResource(r4));
} else if (VersionUtilities.isR2BVer(targetVer)) {
return getBytesDstu2016(cnt, format, VersionConvertor_14_40.convertResource(r4));
} else if (VersionUtilities.isR3Ver(targetVer)) {
return getBytesDstu3(cnt, format, VersionConvertor_30_40.convertResource(r4, false));
} else if (VersionUtilities.isR4Ver(targetVer)) {
return getBytesR4(cnt, format, r4);
} else {
throw new FHIRException("Target Version not supported yet: " + targetVer);
}
}
private static byte[] getBytesDstu2(Content cnt, Manager.FhirFormat format, org.hl7.fhir.dstu2.model.Resource r2) throws IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
switch (format) {
case JSON:
new org.hl7.fhir.dstu2.formats.JsonParser().compose(bs, r2);
return bs.toByteArray();
case XML:
new org.hl7.fhir.dstu2.formats.XmlParser().compose(bs, r2);
return bs.toByteArray();
default:
throw new FHIRException("Unsupported output format: " + cnt.cntType.toString());
}
}
private static byte[] getBytesDstu2016(Content cnt, Manager.FhirFormat format, Resource r2b) throws IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
switch (format) {
case JSON:
new org.hl7.fhir.dstu2016may.formats.JsonParser().compose(bs, r2b);
return bs.toByteArray();
case XML:
new org.hl7.fhir.dstu2016may.formats.XmlParser().compose(bs, r2b);
return bs.toByteArray();
default:
throw new FHIRException("Unsupported output format: " + cnt.cntType.toString());
}
}
private static byte[] getBytesDstu3(Content cnt, Manager.FhirFormat format, org.hl7.fhir.dstu3.model.Resource r3) throws IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
switch (format) {
case JSON:
new org.hl7.fhir.dstu3.formats.JsonParser().compose(bs, r3);
return bs.toByteArray();
case XML:
new org.hl7.fhir.dstu3.formats.XmlParser().compose(bs, r3);
return bs.toByteArray();
default:
throw new FHIRException("Unsupported output format: " + cnt.cntType.toString());
}
}
private static byte[] getBytesR4(Content cnt, Manager.FhirFormat format, org.hl7.fhir.r4.model.Resource r4) throws IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
switch (format) {
case JSON:
new org.hl7.fhir.r4.formats.JsonParser().compose(bs, r4);
return bs.toByteArray();
case XML:
new org.hl7.fhir.r4.formats.XmlParser().compose(bs, r4);
return bs.toByteArray();
default:
throw new FHIRException("Unsupported output format: " + cnt.cntType.toString());
}
}
}

View File

@ -0,0 +1,10 @@
package org.hl7.fhir.validation.cli.services;
import org.hl7.fhir.exceptions.FHIRException;
import java.io.IOException;
public interface IPackageInstaller {
boolean packageExists(String id, String ver) throws IOException, FHIRException;
void loadPackage(String id, String ver) throws IOException, FHIRException;
}

View File

@ -1,16 +1,8 @@
package org.hl7.fhir.validation.cli.services;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.google.gson.JsonObject;
import org.hl7.fhir.convertors.txClient.TerminologyClientFactory;
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.model.CanonicalResource;
@ -22,26 +14,23 @@ import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.VersionUtilities.VersionURLInfo;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import com.google.gson.JsonObject;
import ca.uhn.fhir.util.JsonUtil;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
public interface IPackageInstaller {
boolean packageExists(String id, String ver) throws IOException, FHIRException;
void loadPackage(String id, String ver) throws IOException, FHIRException;
}
List<String> mappingsUris = new ArrayList<>();
private FilesystemPackageCacheManager pcm;
private IWorkerContext context;
private IPackageInstaller installer;
public StandAloneValidatorFetcher(FilesystemPackageCacheManager pcm, IWorkerContext context, IPackageInstaller installer) {
super();
this.pcm = pcm;
@ -50,8 +39,8 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
}
@Override
public Element fetch(Object appContext, String url) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
public Element fetch(Object appContext, String url) throws FHIRException {
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
}
@Override
@ -64,15 +53,15 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
if (!Utilities.isAbsoluteUrl(url)) {
return false;
}
if (url.contains("|")) {
url = url.substring(0, url.lastIndexOf("|"));
}
if (type.equals("uri") && isMappingUri(url)) {
return true;
}
// 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
String pid = null;
String ver = null;
@ -80,19 +69,19 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
if (base == null) {
return !url.startsWith("http://hl7.org/fhir") && !type.equals("canonical");
}
if (base.equals("http://terminology.hl7.org")) {
pid = "hl7.terminology";
pid = "hl7.terminology";
} else if (url.startsWith("http://hl7.org/fhir")) {
pid = pcm.getPackageId(base);
} else {
} else {
pid = pcm.findCanonicalInLocalCache(base);
}
ver = url.contains("|") ? url.substring(url.indexOf("|")+1) : null;
ver = url.contains("|") ? url.substring(url.indexOf("|") + 1) : null;
if (pid == null && Utilities.startsWithInList(url, "http://hl7.org/fhir", "http://terminology.hl7.org")) {
return false;
}
if (url.startsWith("http://hl7.org/fhir")) {
// first possibility: it's a reference to a version specific URL http://hl7.org/fhir/X.X/...
VersionURLInfo vu = VersionUtilities.parseVersionUrl(url);
@ -110,9 +99,8 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
return pi.hasCanonical(url);
}
}
// we don't bother with urls outside fhir space in the standalone validator - we assume they are valid
// we don't bother with urls outside fhir space in the standalone validator - we assume they are valid
return !url.startsWith("http://hl7.org/fhir") && !type.equals("canonical");
}
@ -122,7 +110,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
try {
json = JsonTrackingParser.fetchJson("http://hl7.org/fhir/mappingspaces.json");
for (JsonObject ms : JSONUtil.objects(json, "spaces")) {
mappingsUris.add(JSONUtil.str(ms, "url"));
mappingsUris.add(JSONUtil.str(ms, "url"));
}
} catch (IOException e) {
// frozen R4 list
@ -177,7 +165,7 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
private String findBaseUrl(String url) {
String[] p = url.split("\\/");
for (int i = 1; i< p.length; i++) {
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++) {
@ -192,12 +180,14 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
@Override
public byte[] fetchRaw(String url) throws MalformedURLException, IOException {
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
}
@Override
public void setLocale(Locale locale) {
public IValidatorResourceFetcher setLocale(Locale locale) {
// nothing
return null;
}
@Override
@ -207,20 +197,19 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher {
if (root != null) {
TerminologyClient c;
c = TerminologyClientFactory.makeClient(root, context.getVersion());
return c.read(p[p.length-2], p[p.length-1]);
return c.read(p[p.length - 2], p[p.length - 1]);
} else {
throw new FHIRException("The URL '"+url+"' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
throw new FHIRException("The URL '" + url + "' is not known to the FHIR validator, and has not been provided as part of the setup / parameters");
}
}
private String getRoot(String[] p, String url) {
if (p.length > 3 && Utilities.isValidId(p[p.length-1]) && context.getResourceNames().contains(p[p.length-2])) {
if (p.length > 3 && Utilities.isValidId(p[p.length - 1]) && context.getResourceNames().contains(p[p.length - 2])) {
url = url.substring(0, url.lastIndexOf("/"));
return url.substring(0, url.lastIndexOf("/"));
} else {
return null;
}
}
@Override

View File

@ -1,11 +1,8 @@
package org.hl7.fhir.validation.cli.services;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.r5.context.TerminologyCache;
import org.hl7.fhir.r5.elementmodel.Manager;
@ -15,7 +12,6 @@ import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -26,6 +22,7 @@ import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidationRecord;
import org.hl7.fhir.validation.cli.model.*;
@ -62,8 +59,9 @@ public class ValidationService {
public static VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception {
VersionSourceInformation versions = new VersionSourceInformation();
ValidationEngine ve = new ValidationEngine();
IgLoader igLoader = new IgLoader(ve.getPcm(), ve.getContext(), ve.getVersion(), ve.isDebug());
for (String src : cliContext.getIgs()) {
ve.scanForIgVersion(src, cliContext.isRecursive(), versions);
igLoader.scanForIgVersion(src, cliContext.isRecursive(), versions);
}
ve.scanForVersions(cliContext.getSources(), versions);
return versions;
@ -107,22 +105,6 @@ public class ValidationService {
System.exit(ec > 0 ? 1 : 0);
}
public static void validateScan(CliContext cliContext, ValidationEngine validator) throws Exception {
if (Utilities.noString(cliContext.getOutput()))
throw new Exception("Output parameter required when scanning");
if (!(new File(cliContext.getOutput()).isDirectory()))
throw new Exception("Output '" + cliContext.getOutput() + "' must be a directory when scanning");
System.out.println(" .. scan " + cliContext.getSources() + " against loaded IGs");
Set<String> urls = new HashSet<>();
for (ImplementationGuide ig : validator.getContext().allImplementationGuides()) {
if (ig.getUrl().contains("/ImplementationGuide") && !ig.getUrl().equals("http://hl7.org/fhir/ImplementationGuide/fhir"))
urls.add(ig.getUrl());
}
List<ScanOutputItem> res = validator.validateScan(cliContext.getSources(), urls);
validator.genScanOutput(cliContext.getOutput(), res);
System.out.println("Done. output in " + Utilities.path(cliContext.getOutput(), "scan.html"));
}
public static void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception {
System.out.println(" ...convert");
validator.convert(cliContext.getSources().get(0), cliContext.getOutput());
@ -212,18 +194,19 @@ public class ValidationService {
System.out.print(" Load FHIR v" + cliContext.getSv() + " from " + definitions);
FhirPublication ver = FhirPublication.fromCode(cliContext.getSv());
ValidationEngine validator = new ValidationEngine(definitions, ver, cliContext.getSv(), tt);
IgLoader igLoader = new IgLoader(validator.getPcm(), validator.getContext(), validator.getVersion(), validator.isDebug());
System.out.println(" - "+validator.getContext().countAllCaches()+" resources ("+tt.milestone()+")");
validator.loadIg("hl7.terminology", false);
igLoader.loadIg(validator.getIgs(), validator.getBinaries(), "hl7.terminology", false);
System.out.print(" Terminology server " + cliContext.getTxServer());
String txver = validator.setTerminologyServer(cliContext.getTxServer(), cliContext.getTxLog(), ver);
System.out.println(" - Version "+txver+" ("+tt.milestone()+")");
validator.setDebug(cliContext.isDoDebug());
for (String src : cliContext.getIgs()) {
validator.loadIg(src, cliContext.isRecursive());
igLoader.loadIg(validator.getIgs(), validator.getBinaries(), src, cliContext.isRecursive());
}
System.out.print(" Get set... ");
validator.setQuestionnaireMode(cliContext.getQuestionnaireMode());
validator.setNative(cliContext.isDoNative());
validator.setDoNative(cliContext.isDoNative());
validator.setHintAboutNonMustSupport(cliContext.isHintAboutNonMustSupport());
validator.setAnyExtensionsAllowed(cliContext.isAnyExtensionsAllowed());
validator.setLanguage(cliContext.getLang());

View File

@ -5,6 +5,7 @@ import java.text.NumberFormat;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine;
import org.junit.Test;
@ -21,9 +22,10 @@ public class LoadIgTests {
final String definitions = VersionUtilities.packageForVersion(fhirSpecVersion) + "#" + VersionUtilities.getCurrentVersion(fhirSpecVersion);
ValidationEngine hl7Validator = new ValidationEngine(definitions);
hl7Validator.setNative(false);
hl7Validator.setDoNative(false);
hl7Validator.setAnyExtensionsAllowed(true);
hl7Validator.prepare();
IgLoader igLoader = new IgLoader(hl7Validator.getPcm(), hl7Validator.getContext(), hl7Validator.getVersion(), true);
for (int i = 0; i < DO_TIMES; i++) {
System.gc();
@ -32,7 +34,7 @@ public class LoadIgTests {
System.out.println("max memory " + getTotalMemoryAsMbs() + " MB");
// The method under test:
hl7Validator.loadIg(id + (version != null ? "#" + version : ""), true);
igLoader.loadIg(hl7Validator.getIgs(), hl7Validator.getBinaries(),id + (version != null ? "#" + version : ""), true);
}
} catch (Exception e) {
e.printStackTrace();
@ -51,23 +53,24 @@ public class LoadIgTests {
final String definitions = VersionUtilities.packageForVersion(fhirSpecVersion) + "#" + VersionUtilities.getCurrentVersion(fhirSpecVersion);
ValidationEngine hl7Validator = new ValidationEngine(definitions);
hl7Validator.setNative(false);
hl7Validator.setDoNative(false);
hl7Validator.setAnyExtensionsAllowed(true);
hl7Validator.prepare();
IgLoader igLoader = new IgLoader(hl7Validator.getPcm(), hl7Validator.getContext(), hl7Validator.getVersion(), true);
byte[] b = TextFile.streamToBytes(TestingUtilities.loadTestResourceStream("r5", "snapshot-generation", "t34-expected.xml")); // yes the choice of R5 is deliberate here - it's the same content as R4.
for (int i = 0; i < DO_TIMES; i++) {
System.gc();
for (int j = 0; j < 100; j++) {
hl7Validator.loadResourceByVersion("4.0.1", b, "resource.xml");
igLoader.loadResourceByVersion("4.0.1", b, "resource.xml");
}
System.out.print("loading: allocated memory " + getUsedMemoryAsMbs() + " MB, ");
System.out.print("free memory " + getFreeMemoryAsMbs() + " MB, ");
System.out.println("max memory " + getTotalMemoryAsMbs() + " MB");
// The method under test:
hl7Validator.loadIg(id + (version != null ? "#" + version : ""), true);
igLoader.loadIg(hl7Validator.getIgs(), hl7Validator.getBinaries(), id + (version != null ? "#" + version : ""), true);
}
} catch (Exception e) {
e.printStackTrace();

View File

@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.tests.utilities.TestUtilities;
import org.junit.jupiter.api.Assertions;
@ -157,9 +158,10 @@ public class ValidationEngineTests {
if (!TestUtilities.silent)
System.out.println("Test301USCore: Validate patient300.xml against US-Core");
ValidationEngine ve = new ValidationEngine("hl7.fhir.r3.core#3.0.2", DEF_TX, null, FhirPublication.STU3, "3.0.2");
IgLoader igLoader = new IgLoader(ve.getPcm(), ve.getContext(), ve.getVersion(), true);
if (!TestUtilities.silent)
System.out.println(" .. load USCore");
ve.loadIg("hl7.fhir.us.core#1.0.1", false);
igLoader.loadIg(ve.getIgs(), ve.getBinaries(), "hl7.fhir.us.core#1.0.1", false);
List<String> profiles = new ArrayList<>();
profiles.add("http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient");
OperationOutcome op = ve.validate(FhirFormat.XML, TestingUtilities.loadTestResourceStream("validator", "patient301.xml"), profiles);

View File

@ -12,7 +12,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.NotImplementedException;
@ -56,6 +55,7 @@ import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher;
import org.hl7.fhir.validation.instance.InstanceValidator;
@ -108,6 +108,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
// private static final String DEF_TX = "http://local.fhir.org:960";
private static Map<String, ValidationEngine> ve = new HashMap<>();
private static ValidationEngine vCurr;
private static IgLoader igLoader;
public ValidationTests(String name, JsonObject content) {
this.name = name;
@ -148,6 +149,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
throw new Exception("unknown version " + version);
}
vCurr = ve.get(version);
igLoader = new IgLoader(vCurr.getPcm(), vCurr.getContext(), vCurr.getVersion(), true);
if (TestingUtilities.fcontexts == null) {
TestingUtilities.fcontexts = new HashMap<>();
}
@ -181,9 +183,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
String n = e.getAsString();
InputStream cnt = n.endsWith(".tgz") ? TestingUtilities.loadTestResourceStream("validator", n) : null;
if (cnt != null) {
vCurr.loadPackage(NpmPackage.fromPackage(cnt));
igLoader.loadPackage(NpmPackage.fromPackage(cnt));
} else {
vCurr.loadIg(n, true);
igLoader.loadIg(vCurr.getIgs(), vCurr.getBinaries(), n, true);
}
}
}
@ -289,7 +291,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
if (logical.has("packages")) {
for (JsonElement e : logical.getAsJsonArray("packages")) {
vCurr.loadIg(e.getAsString(), true);
igLoader.loadIg(vCurr.getIgs(), vCurr.getBinaries(), e.getAsString(), true);
}
}
List<ValidationMessage> errorsLogical = new ArrayList<ValidationMessage>();
@ -495,8 +497,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
@Override
public void setLocale(Locale locale) {
public IValidatorResourceFetcher setLocale(Locale locale) {
//do nothing
return null;
}
@Override

View File

@ -110,7 +110,6 @@
<artifactId>hapi-fhir-validation-resources-r4</artifactId>
<version>${hapi_fhir_version}</version>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>