Enable bulk -snapshot and -convert on multiple/wildcard -source (#945)

* initial R4B and 4.3.0 fixes

* Add support for loading ig-folder for R4b. Using R4 stuff for now. Otherwise missing classes and methods

* undo CRLF edits

* enable snapshot generation on multiple sources

* String.valueOf(i)

* Update help.txt

* enable convert generation on multiple sources

* correction in help.txt

* optimize loops

* outfilename to include sourcefilename as prefix

* updated help.txt

* polish-up help.txt

* Tests and add new parameter

* -output XOR -outputSuffix logic

* fix typo

* fixing the logic

* updated help.txt

Co-authored-by: Cees de Jonge <c.de.jonge@philips.com>
Co-authored-by: dotasek <david.otasek@smilecdr.com>
This commit is contained in:
David Simons 2022-10-17 16:51:00 +02:00 committed by GitHub
parent 3f93d7532a
commit 50f61901fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 234 additions and 21 deletions

2
mvnw vendored
View File

@ -212,7 +212,7 @@ else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
jarUrl="http://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac

2
mvnw.cmd vendored
View File

@ -120,7 +120,7 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="http://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)

View File

@ -55,6 +55,8 @@ public class CliContext {
private String map = null;
@JsonProperty("output")
private String output = null;
@JsonProperty("outputSuffix")
private String outputSuffix;
@JsonProperty("htmlOutput")
private String htmlOutput = null;
@JsonProperty("txServer")
@ -122,7 +124,8 @@ public class CliContext {
@JsonProperty("jurisdiction")
private String jurisdiction = JurisdictionUtilities.getJurisdictionFromLocale(Locale.getDefault().getCountry());
@JsonProperty("map")
public String getMap() {
return map;
@ -321,6 +324,17 @@ public class CliContext {
return this;
}
@JsonProperty("outputSuffix")
public String getOutputSuffix() {
return outputSuffix;
}
@JsonProperty("outputSuffix")
public CliContext setOutputSuffix(String outputSuffix) {
this.outputSuffix = outputSuffix;
return this;
}
@JsonProperty("htmlOutput")
public String getHtmlOutput() {
return htmlOutput;
@ -630,6 +644,7 @@ public class CliContext {
Objects.equals(extensions, that.extensions) &&
Objects.equals(map, that.map) &&
Objects.equals(output, that.output) &&
Objects.equals(outputSuffix, that.outputSuffix) &&
Objects.equals(htmlOutput, that.htmlOutput) &&
Objects.equals(txServer, that.txServer) &&
Objects.equals(sv, that.sv) &&
@ -659,7 +674,7 @@ public class CliContext {
@Override
public int hashCode() {
return Objects.hash(doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, wantInvariantsInMessages, map, output, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, fhirpath, snomedCT,
noExtensibleBindingMessages, noInvariants, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, mode, locale, locations, crumbTrails, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars);
}
@ -680,6 +695,7 @@ public class CliContext {
", wantInvariantsInMessages=" + wantInvariantsInMessages +
", map='" + map + '\'' +
", output='" + output + '\'' +
", outputSuffix='" + output + '\'' +
", htmlOutput='" + htmlOutput + '\'' +
", txServer='" + txServer + '\'' +
", sv='" + sv + '\'' +

View File

@ -210,8 +210,25 @@ public class ValidationService {
}
public void convertSources(CliContext cliContext, ValidationEngine validator) throws Exception {
System.out.println(" ...convert");
validator.convert(cliContext.getSources().get(0), cliContext.getOutput());
if (!((cliContext.getOutput() == null) ^ (cliContext.getOutputSuffix() == null))) {
throw new Exception("Convert requires one of {-output, -outputSuffix} parameter to be set");
}
List<String> sources = cliContext.getSources();
if ((sources.size() == 1) && (cliContext.getOutput() != null)) {
System.out.println(" ...convert");
validator.convert(sources.get(0), cliContext.getOutput());
} else {
if (cliContext.getOutputSuffix() == null) {
throw new Exception("Converting multiple/wildcard sources requires a -outputSuffix parameter to be set");
}
for (int i = 0; i < sources.size(); i++) {
String output = sources.get(i) + "." + cliContext.getOutputSuffix();
validator.convert(sources.get(i), output);
System.out.println(" ...convert [" + i + "] (" + sources.get(i) + " to " + output + ")");
}
}
}
public void evaluateFhirpath(CliContext cliContext, ValidationEngine validator) throws Exception {
@ -220,11 +237,28 @@ public class ValidationService {
}
public void generateSnapshot(CliContext cliContext, ValidationEngine validator) throws Exception {
StructureDefinition r = validator.snapshot(cliContext.getSources().get(0), cliContext.getSv());
System.out.println(" ...generated snapshot successfully");
if (cliContext.getOutput() != null) {
validator.handleOutput(r, cliContext.getOutput(), cliContext.getSv());
}
if (!((cliContext.getOutput() == null) ^ (cliContext.getOutputSuffix() == null))) {
throw new Exception("Snapshot generation requires one of {-output, -outputSuffix} parameter to be set");
}
List<String> sources = cliContext.getSources();
if ((sources.size() == 1) && (cliContext.getOutput() != null)) {
StructureDefinition r = validator.snapshot(sources.get(0), cliContext.getSv());
System.out.println(" ...generated snapshot successfully");
validator.handleOutput(r, cliContext.getOutput(), cliContext.getSv());
} else {
if (cliContext.getOutputSuffix() == null) {
throw new Exception("Snapshot generation for multiple/wildcard sources requires a -outputSuffix parameter to be set");
}
for (int i = 0; i < sources.size(); i++) {
StructureDefinition r = validator.snapshot(sources.get(i), cliContext.getSv());
String output = sources.get(i) + "." + cliContext.getOutputSuffix();
validator.handleOutput(r, output, cliContext.getSv());
System.out.println(" ...generated snapshot [" + i + "] successfully (" + sources.get(i) + " to " + output + ")");
}
}
}
public void generateNarrative(CliContext cliContext, ValidationEngine validator) throws Exception {

View File

@ -15,6 +15,8 @@ public class Params {
public static final String VERSION = "-version";
public static final String OUTPUT = "-output";
public static final String OUTPUT_SUFFIX = "-outputSuffix";
public static final String LEVEL = "-level";
public static final String HTML_OUTPUT = "-html-output";
public static final String PROXY = "-proxy";
@ -119,7 +121,13 @@ public class Params {
throw new Error("Specified -output without indicating output file");
else
cliContext.setOutput(args[++i]);
} else if (args[i].equals(HTML_OUTPUT)) {
} else if (args[i].equals(OUTPUT_SUFFIX)) {
if (i + 1 == args.length)
throw new Error("Specified -outputSuffix without indicating output suffix");
else
cliContext.setOutputSuffix(args[++i]);
}
else if (args[i].equals(HTML_OUTPUT)) {
if (i + 1 == args.length)
throw new Error("Specified -html-output without indicating output file");
else

View File

@ -73,6 +73,8 @@ The following parameters are supported:
number of times
-output [file]: a filename for the results (OperationOutcome)
Default: results are sent to the std out.
-outputSuffix [string]: used in -convert and -snapshot to deal with
one or more result files (where -output can only have one)
-debug
Produce additional information about the loading/validation process
-recurse
@ -148,8 +150,14 @@ you must provide a specific parameter:
-convert
-convert requires the parameters -source and -output. ig may be used to provide
a logical model
-convert requires the parameters -source and one of {-output, -outputSuffix}.
-ig may be used to provide a logical model.
If the -source maps to one or more resources, e.g. when using a wildcard,
use -outputSuffix <suffix>, to obtain multiple result files with a
`<sourcefilename>.<suffix>` filename.
Example: `-source *.xml -convert -outputSuffix convert.json` outputs:
`source1.xml.convert.json`, `source2.xml.convert.json`, etc. .
FHIRPath
========
@ -172,8 +180,17 @@ must provide a specific parameter:
-snapshot
-snapshot requires the parameters -defn, -txserver, -source, and -output. ig may
be used to provide necessary base profiles
-snapshot requires the parameters -defn, -txserver, -source, and
one of {-output, -outputSuffix}.
-ig may be used to provide necessary base profiles.
The -output/-outputSuffic filetype (xml, json) may imply a conversion.
If the -source maps to one or more profiles, e.g. when using a wildcard,
use -outputSuffix <suffix>, to obtain multiple result files with a
`<sourcefilename>.<suffix>` filename.
Example: `-source *.xml -snapshot -outputSuffix snapshot.json` outputs:
`source1.xml.snapshot.json`, `source2.xml.snapshot.json`, etc. .
Tests
=====

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.validation.cli.services;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.model.FileInfo;
@ -13,15 +14,24 @@ import org.mockito.Mockito;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.*;
import static org.hl7.fhir.validation.tests.utilities.TestUtilities.getTerminologyCacheDirectory;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class ValidationServiceTest {
final String DUMMY_SOURCE = "dummySource";
final String DUMMY_SOURCE1 = "dummySource1";
final String DUMMY_SOURCE2 = "dummySource2";
final String DUMMY_SOURCE3 = "dummySource3";
final String DUMMY_OUTPUT = "dummyOutput";
final String DUMMY_SV = "1.2.3";
@Test
void validateSources() throws Exception {
SessionCache sessionCache = Mockito.spy(new SessionCache());
@ -34,7 +44,7 @@ class ValidationServiceTest {
ValidationRequest request = new ValidationRequest().setCliContext(new CliContext().setTxCache(getTerminologyCacheDirectory("validationService"))).setFilesToValidate(filesToValidate);
// Validation run 1...nothing cached yet
myService.validateSources(request);
Mockito.verify(sessionCache, Mockito.times(1)).cacheSession(ArgumentMatchers.any(ValidationEngine.class));
verify(sessionCache, Mockito.times(1)).cacheSession(ArgumentMatchers.any(ValidationEngine.class));
Set<String> sessionIds = sessionCache.getSessionIds();
if (sessionIds.stream().findFirst().isPresent()) {
@ -42,7 +52,7 @@ class ValidationServiceTest {
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());
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();
@ -61,4 +71,132 @@ class ValidationServiceTest {
return inputStream;
}
}
@Test
public void convertSingleSource() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextSingleSource();
validationService.convertSources(cliContext.setOutput(DUMMY_OUTPUT),validationEngine);
verify(validationEngine).convert(DUMMY_SOURCE, DUMMY_OUTPUT);
}
@Test
public void convertSingleSourceNoOutput() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextSingleSource();
Exception exception = assertThrows( Exception.class, () -> {
validationService.convertSources(cliContext,validationEngine);
});
}
@Test
public void convertMultipleSourceNoSuffix() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextMultipleSource();
assertThrows( Exception.class, () -> {
validationService.convertSources(cliContext,validationEngine);
}
);
}
@Test
public void convertMultipleSource() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextMultipleSource();
validationService.convertSources(cliContext.setOutputSuffix(DUMMY_OUTPUT),validationEngine);
verify(validationEngine).convert(eq(DUMMY_SOURCE1), and(startsWith(DUMMY_SOURCE1), endsWith(DUMMY_OUTPUT)));
verify(validationEngine).convert(eq(DUMMY_SOURCE2), and(startsWith(DUMMY_SOURCE2), endsWith(DUMMY_OUTPUT)));
verify(validationEngine).convert(eq(DUMMY_SOURCE3), and(startsWith(DUMMY_SOURCE3), endsWith(DUMMY_OUTPUT)));
}
@Test
public void generateSnapshotSingleSource() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
StructureDefinition structureDefinition = mock(StructureDefinition.class);
when(validationEngine.snapshot(DUMMY_SOURCE, DUMMY_SV)).thenReturn(structureDefinition);
CliContext cliContext = getCliContextSingleSource();
validationService.generateSnapshot(cliContext.setOutput(DUMMY_OUTPUT).setSv(DUMMY_SV),validationEngine);
verify(validationEngine).snapshot(DUMMY_SOURCE, DUMMY_SV);
verify(validationEngine).handleOutput(structureDefinition, DUMMY_OUTPUT, DUMMY_SV);
}
@Test
public void generateSnapshotSingleSourceNoOutput() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextSingleSource();
Exception exception = assertThrows( Exception.class, () -> {
validationService.generateSnapshot(cliContext.setOutput(DUMMY_OUTPUT).setSv(DUMMY_SV),validationEngine);
});
}
@Test
public void generateSnapshotMultipleSourceNoSuffix() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
CliContext cliContext = getCliContextMultipleSource();
assertThrows( Exception.class, () -> {
validationService.generateSnapshot(cliContext.setOutput(DUMMY_OUTPUT).setSv(DUMMY_SV),validationEngine);
}
);
}
@Test
public void generateSnapshotMultipleSource() throws Exception {
SessionCache sessionCache = mock(SessionCache.class);
ValidationService validationService = new ValidationService(sessionCache);
ValidationEngine validationEngine = mock(ValidationEngine.class);
StructureDefinition structureDefinition1 = mock(StructureDefinition.class);
StructureDefinition structureDefinition2 = mock(StructureDefinition.class);
StructureDefinition structureDefinition3 = mock(StructureDefinition.class);
when(validationEngine.snapshot(DUMMY_SOURCE1, DUMMY_SV)).thenReturn(structureDefinition1);
when(validationEngine.snapshot(DUMMY_SOURCE2, DUMMY_SV)).thenReturn(structureDefinition2);
when(validationEngine.snapshot(DUMMY_SOURCE3, DUMMY_SV)).thenReturn(structureDefinition3);
CliContext cliContext = getCliContextMultipleSource();
validationService.generateSnapshot(cliContext.setOutputSuffix(DUMMY_OUTPUT).setSv(DUMMY_SV),validationEngine);
verify(validationEngine).snapshot(DUMMY_SOURCE1, DUMMY_SV);
verify(validationEngine).handleOutput(eq(structureDefinition1), and(startsWith(DUMMY_SOURCE1),endsWith(DUMMY_OUTPUT)), eq(DUMMY_SV));
verify(validationEngine).snapshot(DUMMY_SOURCE2, DUMMY_SV);
verify(validationEngine).handleOutput(eq(structureDefinition2), and(startsWith(DUMMY_SOURCE2), endsWith(DUMMY_OUTPUT)), eq(DUMMY_SV));
verify(validationEngine).snapshot(DUMMY_SOURCE3, DUMMY_SV);
verify(validationEngine).handleOutput(eq(structureDefinition3), and(startsWith(DUMMY_SOURCE3), endsWith(DUMMY_OUTPUT)), eq(DUMMY_SV));
}
private CliContext getCliContextSingleSource() {
CliContext cliContext;
cliContext = new CliContext().setSources(Arrays.asList(DUMMY_SOURCE));
return cliContext;
}
private CliContext getCliContextMultipleSource() {
CliContext cliContext;
cliContext = new CliContext().setSources(Arrays.asList(DUMMY_SOURCE1, DUMMY_SOURCE2, DUMMY_SOURCE3));
return cliContext;
}
}