Validation sessions (#443)

* Sessions are working,

* adding javadocs to cache

* These changes were already added, the merge re-added them to the RELEASE_NOTES by mistake.

* cleaning code a little
This commit is contained in:
Mark Iantorno 2021-02-23 14:44:13 -05:00 committed by GitHub
parent f08f4aec85
commit b700d82eab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 436 additions and 127 deletions

View File

@ -0,0 +1 @@
* adding session ids to validator service

View File

@ -100,6 +100,11 @@
<groupId>org.apache.httpcomponents</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId> <artifactId>httpclient</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
<dependency> <dependency>
<groupId>org.fhir</groupId> <groupId>org.fhir</groupId>
<artifactId>ucum</artifactId> <artifactId>ucum</artifactId>

View File

@ -1,5 +1,6 @@
package org.hl7.fhir.validation; package org.hl7.fhir.validation;
import com.google.gson.JsonObject;
import lombok.Getter; import lombok.Getter;
import org.hl7.fhir.convertors.*; import org.hl7.fhir.convertors.*;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
@ -15,12 +16,15 @@ import org.hl7.fhir.utilities.IniFile;
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.utilities.VersionUtilities; import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser; import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.turtle.Turtle; import org.hl7.fhir.utilities.turtle.Turtle;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.hl7.fhir.validation.cli.utils.Common; import org.hl7.fhir.validation.cli.utils.Common;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import org.w3c.dom.Document;
import java.io.*; import java.io.*;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@ -199,6 +203,43 @@ public class IgLoader {
versions.see(readInfoVersion(source.get("version.info")), "version.info in " + src); versions.see(readInfoVersion(source.get("version.info")), "version.info in " + src);
} }
public void scanForVersions(List<String> sources, VersionSourceInformation versions) throws FHIRException, IOException {
List<String> refs = new ArrayList<String>();
ValidatorUtils.parseSources(sources, refs, context);
for (String ref : refs) {
Content cnt = loadContent(ref, "validate", false);
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);
}
try {
if (s.startsWith("{")) {
JsonObject json = JsonTrackingParser.parse(s, null);
if (json.has("fhirVersion")) {
versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in " + ref);
}
} else {
Document doc = ValidatorUtils.parseXml(cnt.focus);
String v = XMLUtil.getNamedChildValue(doc.getDocumentElement(), "fhirVersion");
if (v != null) {
versions.see(VersionUtilities.getMajMin(v), "fhirVersion in " + ref);
}
}
} catch (Exception e) {
// nothing
}
}
}
protected Map<String, byte[]> readZip(InputStream stream) throws IOException { protected Map<String, byte[]> readZip(InputStream stream) throws IOException {
Map<String, byte[]> res = new HashMap<>(); Map<String, byte[]> res = new HashMap<>();
ZipInputStream zip = new ZipInputStream(stream); ZipInputStream zip = new ZipInputStream(stream);
@ -645,7 +686,7 @@ public class IgLoader {
else if (fn.endsWith(".json") && !fn.endsWith("template.json")) else if (fn.endsWith(".json") && !fn.endsWith("template.json"))
r = new JsonParser().parse(new ByteArrayInputStream(content)); r = new JsonParser().parse(new ByteArrayInputStream(content));
else if (fn.endsWith(".txt")) else if (fn.endsWith(".txt"))
r = new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(content), fn); r = new StructureMapUtilities(getContext(), null, null).parse(TextFile.bytesToString(content), fn);
else if (fn.endsWith(".map")) else if (fn.endsWith(".map"))
r = new StructureMapUtilities(null).parse(new String(content), fn); r = new StructureMapUtilities(null).parse(new String(content), fn);
else else

View File

@ -193,49 +193,12 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
igLoader = new IgLoader(getPcm(), getContext(), getVersion(), isDebug()); igLoader = new IgLoader(getPcm(), getContext(), getVersion(), isDebug());
} }
public ValidationEngine(String src, FhirPublication version, String vString, TimeTracker tt) throws FHIRException, IOException, URISyntaxException { public ValidationEngine(String src, String vString, TimeTracker tt) throws FHIRException, IOException, URISyntaxException {
loadCoreDefinitions(src, false, tt); loadCoreDefinitions(src, false, tt);
setVersion(vString); setVersion(vString);
igLoader = new IgLoader(getPcm(), getContext(), getVersion(), isDebug()); igLoader = new IgLoader(getPcm(), getContext(), getVersion(), isDebug());
} }
public void scanForVersions(List<String> sources, VersionSourceInformation versions) throws FHIRException, IOException {
List<String> refs = new ArrayList<String>();
ValidatorUtils.parseSources(sources, refs, context);
for (String ref : refs) {
Content cnt = igLoader.loadContent(ref, "validate", false);
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);
}
try {
if (s.startsWith("{")) {
JsonObject json = JsonTrackingParser.parse(s, null);
if (json.has("fhirVersion")) {
versions.see(VersionUtilities.getMajMin(JSONUtil.str(json, "fhirVersion")), "fhirVersion in " + ref);
}
} else {
Document doc = ValidatorUtils.parseXml(cnt.focus);
String v = XMLUtil.getNamedChildValue(doc.getDocumentElement(), "fhirVersion");
if (v != null) {
versions.see(VersionUtilities.getMajMin(v), "fhirVersion in " + ref);
}
}
} catch (Exception e) {
// nothing
}
}
}
private void loadCoreDefinitions(String src, boolean recursive, TimeTracker tt) throws FHIRException, IOException { private void loadCoreDefinitions(String src, boolean recursive, TimeTracker tt) throws FHIRException, IOException {
NpmPackage npm = getPcm().loadPackage(src, null); NpmPackage npm = getPcm().loadPackage(src, null);
if (npm != null) { if (npm != null) {
@ -252,7 +215,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
initContext(tt); initContext(tt);
} }
public void initContext(TimeTracker tt) throws IOException, FileNotFoundException { public void initContext(TimeTracker tt) throws IOException {
context.setCanNoTS(true); context.setCanNoTS(true);
context.setCacheId(UUID.randomUUID().toString()); context.setCacheId(UUID.randomUUID().toString());
context.setAllowLoadingDuplicates(true); // because of Forge context.setAllowLoadingDuplicates(true); // because of Forge

View File

@ -94,6 +94,8 @@ public class ValidatorCli {
public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes"; public static final String JAVA_DISABLED_PROXY_SCHEMES = "jdk.http.auth.proxying.disabledSchemes";
public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies"; public static final String JAVA_USE_SYSTEM_PROXIES = "java.net.useSystemProxies";
private static ValidationService validationService = new ValidationService();
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
TimeTracker tt = new TimeTracker(); TimeTracker tt = new TimeTracker();
TimeTracker.Session tts = tt.start("Loading"); TimeTracker.Session tts = tt.start("Loading");
@ -181,42 +183,42 @@ public class ValidatorCli {
private static void doLeftRightComparison(String[] args, CliContext cliContext, TimeTracker tt) throws Exception { private static void doLeftRightComparison(String[] args, CliContext cliContext, TimeTracker tt) throws Exception {
Display.printCliArgumentsAndInfo(args); Display.printCliArgumentsAndInfo(args);
if (cliContext.getSv() == null) { if (cliContext.getSv() == null) {
cliContext.setSv(ValidationService.determineVersion(cliContext)); cliContext.setSv(validationService.determineVersion(cliContext));
} }
String v = VersionUtilities.getCurrentVersion(cliContext.getSv()); String v = VersionUtilities.getCurrentVersion(cliContext.getSv());
String definitions = VersionUtilities.packageForVersion(v) + "#" + v; String definitions = VersionUtilities.packageForVersion(v) + "#" + v;
ValidationEngine validator = ValidationService.getValidator(cliContext, definitions, tt); ValidationEngine validator = validationService.initializeValidator(cliContext, definitions, tt);
ComparisonService.doLeftRightComparison(args, Params.getParam(args, Params.DESTINATION), validator); ComparisonService.doLeftRightComparison(args, Params.getParam(args, Params.DESTINATION), validator);
} }
private static void doValidation(TimeTracker tt, TimeTracker.Session tts, CliContext cliContext) throws Exception { private static void doValidation(TimeTracker tt, TimeTracker.Session tts, CliContext cliContext) throws Exception {
if (cliContext.getSv() == null) { if (cliContext.getSv() == null) {
cliContext.setSv(ValidationService.determineVersion(cliContext)); cliContext.setSv(validationService.determineVersion(cliContext));
} }
System.out.println("Loading"); System.out.println("Loading");
// Comment this out because definitions filename doesn't necessarily contain version (and many not even be 14 characters long). // 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 // 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, tt); ValidationEngine validator = validationService.initializeValidator(cliContext, definitions, tt);
tts.end(); tts.end();
switch (cliContext.getMode()) { switch (cliContext.getMode()) {
case TRANSFORM: case TRANSFORM:
ValidationService.transform(cliContext, validator); validationService.transform(cliContext, validator);
break; break;
case NARRATIVE: case NARRATIVE:
ValidationService.generateNarrative(cliContext, validator); validationService.generateNarrative(cliContext, validator);
break; break;
case SNAPSHOT: case SNAPSHOT:
ValidationService.generateSnapshot(cliContext, validator); validationService.generateSnapshot(cliContext, validator);
break; break;
case CONVERT: case CONVERT:
ValidationService.convertSources(cliContext, validator); validationService.convertSources(cliContext, validator);
break; break;
case FHIRPATH: case FHIRPATH:
ValidationService.evaluateFhirpath(cliContext, validator); validationService.evaluateFhirpath(cliContext, validator);
break; break;
case VERSION: case VERSION:
ValidationService.transformVersion(cliContext, validator); validationService.transformVersion(cliContext, validator);
break; break;
case VALIDATION: case VALIDATION:
case SCAN: case SCAN:
@ -232,7 +234,7 @@ public class ValidatorCli {
Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(), validator.getIgLoader(), validator.getFhirPathEngine()); Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(), validator.getIgLoader(), validator.getFhirPathEngine());
validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources()); validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
} else { } else {
ValidationService.validateSources(cliContext, validator); validationService.validateSources(cliContext, validator);
} }
break; break;
} }

View File

@ -18,11 +18,19 @@ public class ValidationRequest {
return cliContext; return cliContext;
} }
@JsonProperty("sessionId")
public String sessionId;
public ValidationRequest() {} public ValidationRequest() {}
public ValidationRequest(CliContext cliContext, List<FileInfo> filesToValidate) { public ValidationRequest(CliContext cliContext, List<FileInfo> filesToValidate) {
this(cliContext, filesToValidate, null);
}
public ValidationRequest(CliContext cliContext, List<FileInfo> filesToValidate, String sessionToken) {
this.cliContext = cliContext; this.cliContext = cliContext;
this.filesToValidate = filesToValidate; this.filesToValidate = filesToValidate;
this.sessionId = sessionToken;
} }
@JsonProperty("cliContext") @JsonProperty("cliContext")
@ -42,6 +50,17 @@ public class ValidationRequest {
return this; return this;
} }
@JsonProperty("sessionId")
public String getSessionId() {
return sessionId;
}
@JsonProperty("sessionId")
public ValidationRequest setSessionId(String sessionId) {
this.sessionId = sessionId;
return this;
}
public String listSourceFiles() { public String listSourceFiles() {
List<String> fileNames = new ArrayList<>(); List<String> fileNames = new ArrayList<>();
for (FileInfo fp : filesToValidate) { for (FileInfo fp : filesToValidate) {

View File

@ -10,10 +10,18 @@ public class ValidationResponse {
@JsonProperty("outcomes") @JsonProperty("outcomes")
public List<ValidationOutcome> outcomes = new ArrayList<>(); public List<ValidationOutcome> outcomes = new ArrayList<>();
@JsonProperty("sessionToken")
public String sessionToken;
public ValidationResponse() {} public ValidationResponse() {}
public ValidationResponse(List<ValidationOutcome> outcomes) { public ValidationResponse(List<ValidationOutcome> outcomes) {
this(outcomes, null);
}
public ValidationResponse(List<ValidationOutcome> outcomes, String sessionToken) {
this.outcomes = outcomes; this.outcomes = outcomes;
this.sessionToken = sessionToken;
} }
@JsonProperty("outcomes") @JsonProperty("outcomes")
@ -27,6 +35,17 @@ public class ValidationResponse {
return this; return this;
} }
@JsonProperty("sessionToken")
public String getSessionToken() {
return sessionToken;
}
@JsonProperty("sessionToken")
public ValidationResponse setSessionToken(String sessionToken) {
this.sessionToken = sessionToken;
return this;
}
public ValidationResponse addOutcome(ValidationOutcome outcome) { public ValidationResponse addOutcome(ValidationOutcome outcome) {
if (outcomes == null) { if (outcomes == null) {
outcomes = new ArrayList<>(); outcomes = new ArrayList<>();

View File

@ -0,0 +1,95 @@
package org.hl7.fhir.validation.cli.services;
import org.apache.commons.collections4.map.PassiveExpiringMap;
import org.hl7.fhir.validation.ValidationEngine;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* SessionCache for storing and retrieving ValidationEngine instances, so callers do not have to re-instantiate a new
* instance for each validation request.
*/
public class SessionCache {
protected static final long TIME_TO_LIVE = 60;
protected static final TimeUnit TIME_UNIT = TimeUnit.MINUTES;
private final PassiveExpiringMap<String, ValidationEngine> cachedSessions;
public SessionCache() {
cachedSessions = new PassiveExpiringMap<>(TIME_TO_LIVE, TIME_UNIT);
}
/**
* @param sessionLength the constant amount of time an entry is available before it expires. A negative value results
* in entries that NEVER expire. A zero value results in entries that ALWAYS expire.
* @param sessionLengthUnit the unit of time for the timeToLive parameter, must not be null
*/
public SessionCache(long sessionLength, TimeUnit sessionLengthUnit) {
cachedSessions = new PassiveExpiringMap<>(sessionLength, sessionLengthUnit);
}
/**
* Stores the initialized {@link ValidationEngine} in the cache. Returns the session id that will be associated with
* this instance.
* @param validationEngine {@link ValidationEngine}
* @return The {@link String} id associated with the stored instance.
*/
public String cacheSession(ValidationEngine validationEngine) {
String generatedId = generateID();
cachedSessions.put(generatedId, validationEngine);
return generatedId;
}
/**
* Stores the initialized {@link ValidationEngine} in the cache with the passed in id as the key. If a null key is
* passed in, a new key is generated and returned.
* @param sessionId The {@link String} key to associate with this stored {@link ValidationEngine}
* @param validationEngine The {@link ValidationEngine} instance to cache.
* @return The {@link String} id that will be associated with the stored {@link ValidationEngine}
*/
public String cacheSession(String sessionId, ValidationEngine validationEngine) {
if(sessionId == null) {
sessionId = cacheSession(validationEngine);
} else {
cachedSessions.put(sessionId, validationEngine);
}
return sessionId;
}
/**
* Checks if the passed in {@link String} id exists in the set of stored session id.
* @param sessionId The {@link String} id to search for.
* @return {@link Boolean#TRUE} if such id exists.
*/
public boolean sessionExists(String sessionId) {
return cachedSessions.containsKey(sessionId);
}
/**
* Returns the stored {@link ValidationEngine} associated with the passed in session id, if one such instance exists.
* @param sessionId The {@link String} session id.
* @return The {@link ValidationEngine} associated with the passed in id, or null if none exists.
*/
public ValidationEngine fetchSessionValidatorEngine(String sessionId) {
return cachedSessions.get(sessionId);
}
/**
* Returns the set of stored session ids.
* @return {@link Set} of session ids.
*/
public Set<String> getSessionIds() {
return cachedSessions.keySet();
}
/**
* Session ids generated internally are UUID {@link String}.
* @return A new {@link String} session id.
*/
private String generateID() {
return UUID.randomUUID().toString();
}
}

View File

@ -1,26 +1,20 @@
package org.hl7.fhir.validation.cli.services; package org.hl7.fhir.validation.cli.services;
import java.io.FileOutputStream; import org.hl7.fhir.r5.context.SimpleWorkerContext;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.context.TerminologyCache; import org.hl7.fhir.r5.context.TerminologyCache;
import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.formats.IParser; import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.FhirPublication;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.TimeTracker;
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.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.ToolsVersion;
import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.IgLoader; import org.hl7.fhir.validation.IgLoader;
import org.hl7.fhir.validation.ValidationEngine; import org.hl7.fhir.validation.ValidationEngine;
@ -29,14 +23,32 @@ import org.hl7.fhir.validation.cli.model.*;
import org.hl7.fhir.validation.cli.utils.EngineMode; import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
public class ValidationService { public class ValidationService {
public static ValidationResponse validateSources(ValidationRequest request) throws Exception { private final SessionCache sessionCache;
public ValidationService() {
sessionCache = new SessionCache();
}
protected ValidationService(SessionCache cache) {
this.sessionCache = cache;
}
public ValidationResponse validateSources(ValidationRequest request) throws Exception {
if (request.getCliContext().getSv() == null) { if (request.getCliContext().getSv() == null) {
request.getCliContext().setSv(ValidationService.determineVersion(request.getCliContext())); String sv = determineVersion(request.getCliContext(), request.sessionId);
request.getCliContext().setSv(sv);
} }
String definitions = VersionUtilities.packageForVersion(request.getCliContext().getSv()) + "#" + VersionUtilities.getCurrentVersion(request.getCliContext().getSv()); String definitions = VersionUtilities.packageForVersion(request.getCliContext().getSv()) + "#" + VersionUtilities.getCurrentVersion(request.getCliContext().getSv());
ValidationEngine validator = ValidationService.getValidator(request.getCliContext(), definitions, new TimeTracker());
String sessionId = initializeValidator(request.getCliContext(), definitions, new TimeTracker(), request.sessionId);
ValidationEngine validator = sessionCache.fetchSessionValidatorEngine(sessionId);
if (request.getCliContext().getProfiles().size() > 0) { if (request.getCliContext().getProfiles().size() > 0) {
System.out.println(" .. validate " + request.listSourceFiles() + " against " + request.getCliContext().getProfiles().toString()); System.out.println(" .. validate " + request.listSourceFiles() + " against " + request.getCliContext().getProfiles().toString());
@ -56,23 +68,25 @@ public class ValidationService {
return response; return response;
} }
public static VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception { public VersionSourceInformation scanForVersions(CliContext cliContext) throws Exception {
VersionSourceInformation versions = new VersionSourceInformation(); VersionSourceInformation versions = new VersionSourceInformation();
ValidationEngine ve = new ValidationEngine(); IgLoader igLoader = new IgLoader(
IgLoader igLoader = new IgLoader(ve.getPcm(), ve.getContext(), ve.getVersion(), ve.isDebug()); new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION),
SimpleWorkerContext.fromNothing(),
null);
for (String src : cliContext.getIgs()) { for (String src : cliContext.getIgs()) {
igLoader.scanForIgVersion(src, cliContext.isRecursive(), versions); igLoader.scanForIgVersion(src, cliContext.isRecursive(), versions);
} }
ve.scanForVersions(cliContext.getSources(), versions); igLoader.scanForVersions(cliContext.getSources(), versions);
return versions; return versions;
} }
public static void validateSources(CliContext cliContext, ValidationEngine validator) throws Exception { public void validateSources(CliContext cliContext, ValidationEngine validator) throws Exception {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
List<ValidationRecord> records = new ArrayList<>(); List<ValidationRecord> records = new ArrayList<>();
Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), records); Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), records);
int ec = 0; int ec = 0;
System.out.println("Done. "+validator.getContext().clock().report()); System.out.println("Done. " + validator.getContext().clock().report());
System.out.println(); System.out.println();
if (cliContext.getOutput() == null) { if (cliContext.getOutput() == null) {
@ -98,24 +112,24 @@ public class ValidationService {
s.close(); s.close();
} }
if (cliContext.getHtmlOutput() != null) { if (cliContext.getHtmlOutput() != null) {
String html = new HTMLOutputGenerator(records).generate(System.currentTimeMillis()-start); String html = new HTMLOutputGenerator(records).generate(System.currentTimeMillis() - start);
TextFile.stringToFile(html, cliContext.getHtmlOutput()); TextFile.stringToFile(html, cliContext.getHtmlOutput());
System.out.println("HTML Summary in "+cliContext.getHtmlOutput()); System.out.println("HTML Summary in " + cliContext.getHtmlOutput());
} }
System.exit(ec > 0 ? 1 : 0); System.exit(ec > 0 ? 1 : 0);
} }
public static void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception { public void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception {
System.out.println(" ...convert"); System.out.println(" ...convert");
validator.convert(cliContext.getSources().get(0), cliContext.getOutput()); validator.convert(cliContext.getSources().get(0), cliContext.getOutput());
} }
public static void evaluateFhirpath(CliContext cliContext, ValidationEngine validator) throws Exception { public void evaluateFhirpath(CliContext cliContext, ValidationEngine validator) throws Exception {
System.out.println(" ...evaluating " + cliContext.getFhirpath()); System.out.println(" ...evaluating " + cliContext.getFhirpath());
System.out.println(validator.evaluateFhirPath(cliContext.getSources().get(0), cliContext.getFhirpath())); System.out.println(validator.evaluateFhirPath(cliContext.getSources().get(0), cliContext.getFhirpath()));
} }
public static void generateSnapshot(CliContext cliContext, ValidationEngine validator) throws Exception { public void generateSnapshot(CliContext cliContext, ValidationEngine validator) throws Exception {
StructureDefinition r = validator.snapshot(cliContext.getSources().get(0), cliContext.getSv()); StructureDefinition r = validator.snapshot(cliContext.getSources().get(0), cliContext.getSv());
System.out.println(" ...generated snapshot successfully"); System.out.println(" ...generated snapshot successfully");
if (cliContext.getOutput() != null) { if (cliContext.getOutput() != null) {
@ -123,7 +137,7 @@ public class ValidationService {
} }
} }
public static void generateNarrative(CliContext cliContext, ValidationEngine validator) throws Exception { public void generateNarrative(CliContext cliContext, ValidationEngine validator) throws Exception {
DomainResource r = validator.generate(cliContext.getSources().get(0), cliContext.getSv()); DomainResource r = validator.generate(cliContext.getSources().get(0), cliContext.getSv());
System.out.println(" ...generated narrative successfully"); System.out.println(" ...generated narrative successfully");
if (cliContext.getOutput() != null) { if (cliContext.getOutput() != null) {
@ -131,7 +145,7 @@ public class ValidationService {
} }
} }
public static void transform(CliContext cliContext, ValidationEngine validator) throws Exception { public void transform(CliContext cliContext, ValidationEngine validator) throws Exception {
if (cliContext.getSources().size() > 1) if (cliContext.getSources().size() > 1)
throw new Exception("Can only have one source when doing a transform (found " + cliContext.getSources() + ")"); throw new Exception("Can only have one source when doing a transform (found " + cliContext.getSources() + ")");
if (cliContext.getTxServer() == null) if (cliContext.getTxServer() == null)
@ -166,7 +180,7 @@ public class ValidationService {
} }
} }
public static void transformVersion(CliContext cliContext, ValidationEngine validator) throws Exception { public void transformVersion(CliContext cliContext, ValidationEngine validator) throws Exception {
if (cliContext.getSources().size() > 1) { if (cliContext.getSources().size() > 1) {
throw new Exception("Can only have one source when converting versions (found " + cliContext.getSources() + ")"); throw new Exception("Can only have one source when converting versions (found " + cliContext.getSources() + ")");
} }
@ -189,44 +203,54 @@ public class ValidationService {
} }
} }
public static ValidationEngine getValidator(CliContext cliContext, String definitions, TimeTracker tt) throws Exception { public ValidationEngine initializeValidator(CliContext cliContext, String definitions, TimeTracker tt) throws Exception {
tt.milestone(); return sessionCache.fetchSessionValidatorEngine(initializeValidator(cliContext, definitions, tt, null));
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()+")");
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()) {
igLoader.loadIg(validator.getIgs(), validator.getBinaries(), src, cliContext.isRecursive());
}
System.out.print(" Get set... ");
validator.setQuestionnaireMode(cliContext.getQuestionnaireMode());
validator.setDoNative(cliContext.isDoNative());
validator.setHintAboutNonMustSupport(cliContext.isHintAboutNonMustSupport());
validator.setAnyExtensionsAllowed(cliContext.isAnyExtensionsAllowed());
validator.setLanguage(cliContext.getLang());
validator.setLocale(cliContext.getLocale());
validator.setSnomedExtension(cliContext.getSnomedCTCode());
validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
validator.setSecurityChecks(cliContext.isSecurityChecks());
validator.setCrumbTrails(cliContext.isCrumbTrails());
validator.setShowTimes(cliContext.isShowTimes());
validator.setFetcher(new StandAloneValidatorFetcher(validator.getPcm(), validator.getContext(), validator));
validator.getBundleValidationRules().addAll(cliContext.getBundleValidationRules());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
validator.prepare(); // generate any missing snapshots
System.out.println(" go ("+tt.milestone()+")");
return validator;
} }
public static int displayOperationOutcome(OperationOutcome oo, boolean hasMultiples) { public String initializeValidator(CliContext cliContext, String definitions, TimeTracker tt, String sessionId) throws Exception {
tt.milestone();
if (!sessionCache.sessionExists(sessionId)) {
System.out.println("No such cached session exists for session id " + sessionId + ", re-instantiating validator.");
System.out.print(" Load FHIR v" + cliContext.getSv() + " from " + definitions);
ValidationEngine validator = new ValidationEngine(definitions, cliContext.getSv(), tt);
sessionId = sessionCache.cacheSession(validator);
FhirPublication ver = FhirPublication.fromCode(cliContext.getSv());
IgLoader igLoader = new IgLoader(validator.getPcm(), validator.getContext(), validator.getVersion(), validator.isDebug());
System.out.println(" - " + validator.getContext().countAllCaches() + " resources (" + tt.milestone() + ")");
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()) {
igLoader.loadIg(validator.getIgs(), validator.getBinaries(), src, cliContext.isRecursive());
}
System.out.print(" Get set... ");
validator.setQuestionnaireMode(cliContext.getQuestionnaireMode());
validator.setDoNative(cliContext.isDoNative());
validator.setHintAboutNonMustSupport(cliContext.isHintAboutNonMustSupport());
validator.setAnyExtensionsAllowed(cliContext.isAnyExtensionsAllowed());
validator.setLanguage(cliContext.getLang());
validator.setLocale(cliContext.getLocale());
validator.setSnomedExtension(cliContext.getSnomedCTCode());
validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences());
validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages());
validator.setSecurityChecks(cliContext.isSecurityChecks());
validator.setCrumbTrails(cliContext.isCrumbTrails());
validator.setShowTimes(cliContext.isShowTimes());
validator.setFetcher(new StandAloneValidatorFetcher(validator.getPcm(), validator.getContext(), validator));
validator.getBundleValidationRules().addAll(cliContext.getBundleValidationRules());
TerminologyCache.setNoCaching(cliContext.isNoInternalCaching());
validator.prepare(); // generate any missing snapshots
System.out.println(" go (" + tt.milestone() + ")");
} else {
System.out.println("Cached session exists for session id " + sessionId + ", returning stored validator session id.");
}
return sessionId;
}
public int displayOperationOutcome(OperationOutcome oo, boolean hasMultiples) {
int error = 0; int error = 0;
int warn = 0; int warn = 0;
int info = 0; int info = 0;
@ -240,29 +264,29 @@ public class ValidationService {
else else
info++; info++;
} }
if (hasMultiples) { if (hasMultiples) {
System.out.print("-- "); System.out.print("-- ");
System.out.print(file); System.out.print(file);
System.out.print(" --"); System.out.print(" --");
System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length()+6))); System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6)));
} }
System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info)+" notes"); System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes");
for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) {
System.out.println(getIssueSummary(issue)); System.out.println(getIssueSummary(issue));
} }
if (hasMultiples) { if (hasMultiples) {
System.out.print("---"); System.out.print("---");
System.out.print(Utilities.padLeft("", '-', file.length())); System.out.print(Utilities.padLeft("", '-', file.length()));
System.out.print("---"); System.out.print("---");
System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length()+6))); System.out.println(Utilities.padLeft("", '-', Integer.max(38, file.length() + 6)));
System.out.println(); System.out.println();
} }
return error; return error;
} }
private static String getIssueSummary(OperationOutcome.OperationOutcomeIssueComponent issue) { private String getIssueSummary(OperationOutcome.OperationOutcomeIssueComponent issue) {
String loc = null; String loc;
if (issue.hasExpression()) { if (issue.hasExpression()) {
int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1); int line = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_LINE, -1);
int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1); int col = ToolingExtensions.readIntegerExtension(issue, ToolingExtensions.EXT_ISSUE_COL, -1);
@ -277,12 +301,16 @@ public class ValidationService {
return " " + issue.getSeverity().getDisplay() + " @ " + loc + " : " + issue.getDetails().getText(); return " " + issue.getSeverity().getDisplay() + " @ " + loc + " : " + issue.getDetails().getText();
} }
public static String determineVersion(CliContext cliContext) throws Exception { public String determineVersion(CliContext cliContext) throws Exception {
return determineVersion(cliContext, null);
}
public String determineVersion(CliContext cliContext, String sessionId) throws Exception {
if (cliContext.getMode() != EngineMode.VALIDATION) { if (cliContext.getMode() != EngineMode.VALIDATION) {
return "current"; return "current";
} }
System.out.println("Scanning for versions (no -version parameter):"); System.out.println("Scanning for versions (no -version parameter):");
VersionSourceInformation versions = ValidationService.scanForVersions(cliContext); VersionSourceInformation versions = scanForVersions(cliContext);
for (String s : versions.getReport()) { for (String s : versions.getReport()) {
if (!s.equals("(nothing found)")) { if (!s.equals("(nothing found)")) {
System.out.println(" " + s); System.out.println(" " + s);

View File

@ -87,7 +87,7 @@ public class Common {
public static ValidationEngine getValidationEngine(String version, String txServer, String definitions, String txLog, TimeTracker tt) throws Exception { public static ValidationEngine getValidationEngine(String version, String txServer, String definitions, String txLog, TimeTracker tt) throws Exception {
System.out.println("Loading (v = " + version + ", tx server -> " + txServer + ")"); System.out.println("Loading (v = " + version + ", tx server -> " + txServer + ")");
ValidationEngine ve = new ValidationEngine(definitions, FhirPublication.fromCode(version), version, tt); ValidationEngine ve = new ValidationEngine(definitions, version, tt);
ve.connectToTSServer(txServer, txLog, FhirPublication.fromCode(version)); ve.connectToTSServer(txServer, txLog, FhirPublication.fromCode(version));
return ve; return ve;
} }

View File

@ -8,8 +8,8 @@ import java.util.List;
public class VersionSourceInformation { public class VersionSourceInformation {
private List<String> report = new ArrayList<>(); private final List<String> report = new ArrayList<>();
private List<String> versions = new ArrayList<>(); private final List<String> versions = new ArrayList<>();
public void see(String version, String src) { public void see(String version, String src) {
version = VersionUtilities.getMajMin(version); version = VersionUtilities.getMajMin(version);

View File

@ -0,0 +1,51 @@
package org.hl7.fhir.validation.cli.services;
import org.hl7.fhir.validation.ValidationEngine;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
class SessionCacheTest {
@Test
@DisplayName("test session expiration works")
void expiredSession() throws IOException, InterruptedException {
final long EXPIRE_TIME = 5L;
SessionCache cache = new SessionCache(EXPIRE_TIME, TimeUnit.SECONDS);
ValidationEngine testEngine = new ValidationEngine();
String sessionId = cache.cacheSession(testEngine);
TimeUnit.SECONDS.sleep(EXPIRE_TIME + 1L);
Assertions.assertNull(cache.fetchSessionValidatorEngine(sessionId));
}
@Test
@DisplayName("test session caching works")
void cachedSession() throws IOException {
final long EXPIRE_TIME = 5L;
SessionCache cache = new SessionCache(EXPIRE_TIME, TimeUnit.SECONDS);
ValidationEngine testEngine = new ValidationEngine();
String sessionId = cache.cacheSession(testEngine);
Assertions.assertEquals(testEngine, cache.fetchSessionValidatorEngine(sessionId));
}
@Test
@DisplayName("test session exists")
void sessionExists() throws IOException {
SessionCache cache = new SessionCache();
ValidationEngine testEngine = new ValidationEngine();
String sessionId = cache.cacheSession(testEngine);
Assertions.assertTrue(cache.sessionExists(sessionId));
Assertions.assertFalse(cache.sessionExists(UUID.randomUUID().toString()));
}
@Test
@DisplayName("test null session test id returns false")
void testNullSessionExists() {
SessionCache cache = new SessionCache();
Assertions.assertFalse(cache.sessionExists(null));
}
}

View File

@ -0,0 +1,67 @@
package org.hl7.fhir.validation.cli.services;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.model.FileInfo;
import org.hl7.fhir.validation.cli.model.ValidationRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
class ValidationServiceTest {
@Test
void validateSources() throws Exception {
SessionCache sessionCache = Mockito.spy(new SessionCache());
ValidationService myService = new ValidationService(sessionCache);
String resource = IOUtils.toString(getFileFromResourceAsStream("detected_issues.json"), StandardCharsets.UTF_8);
List<FileInfo> filesToValidate = new ArrayList<>();
filesToValidate.add(new FileInfo().setFileName("test_resource.json").setFileContent(resource).setFileType(Manager.FhirFormat.JSON.getExtension()));
ValidationRequest request = new ValidationRequest().setCliContext(new CliContext()).setFilesToValidate(filesToValidate);
// Validation run 1...nothing cached yet
myService.validateSources(request);
Mockito.verify(sessionCache, Mockito.times(1)).cacheSession(ArgumentMatchers.any(ValidationEngine.class));
Set<String> sessionIds = sessionCache.getSessionIds();
if (sessionIds.stream().findFirst().isPresent()) {
// Verify that after 1 run there is only one entry within the cache
Assertions.assertEquals(1, sessionIds.size());
myService.validateSources(request);
// Verify that the cache has been called on once with the id created in the first run
Mockito.verify(sessionCache, Mockito.times(1)).fetchSessionValidatorEngine(sessionIds.stream().findFirst().get());
} else {
// If no sessions exist within the cache after a run, we auto-fail.
fail();
}
}
private InputStream getFileFromResourceAsStream(String fileName) {
// The class loader that loaded the class
ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream(fileName);
// the stream holding the file content
if (inputStream == null) {
throw new IllegalArgumentException("file not found! " + fileName);
} else {
return inputStream;
}
}
}

View File

@ -0,0 +1,18 @@
{
"resourceType": "DetectedIssue",
"identifier": [ {
"system": "http://covidcare.au/app/checkin",
"value": "7"
} ],
"status": "final",
"patient": "Patient/4912",
"identifiedDateTime": "3020-10-27T13:33:15+11:00",
"code": {
"coding": [ {
"system": "http://covidcare.au/app/alert",
"code": "trendNegative",
"display": "CovidCare: Vital signs trend negative alert: re-check recommended"
} ],
"text": "CovidCare alert"
}
}