This commit is contained in:
Stuart McGrigor 2024-09-26 15:37:57 -04:00 committed by GitHub
commit 478801ac48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 83 additions and 16 deletions

View File

@ -0,0 +1 @@

View File

@ -35,6 +35,7 @@ import org.apache.commons.cli.ParseException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.text.WordUtils; import org.apache.commons.lang3.text.WordUtils;
import org.fusesource.jansi.Ansi.Color; import org.fusesource.jansi.Ansi.Color;
import org.hl7.fhir.common.hapi.validation.support.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
@ -86,6 +87,8 @@ public class ValidateCommand extends BaseCommand {
"igpack", "igpack",
true, true,
"If specified, provides the filename of an IGPack file to include in validation"); "If specified, provides the filename of an IGPack file to include in validation");
addOptionalOption(
retVal, null, "oneCode", false, "Validate allowing one coding to satisfy CodeableConcept binding");
addOptionalOption(retVal, "x", "xsd", false, "Validate using Schemas"); addOptionalOption(retVal, "x", "xsd", false, "Validate using Schemas");
addOptionalOption(retVal, "s", "sch", false, "Validate using Schematrons"); addOptionalOption(retVal, "s", "sch", false, "Validate using Schematrons");
addOptionalOption(retVal, "e", "encoding", "encoding", "File encoding (default is UTF-8)"); addOptionalOption(retVal, "e", "encoding", "encoding", "File encoding (default is UTF-8)");
@ -149,20 +152,23 @@ public class ValidateCommand extends BaseCommand {
} }
if (theCommandLine.hasOption("p")) { if (theCommandLine.hasOption("p")) {
ValidationSupportChain validationSupportChain;
switch (ctx.getVersion().getVersion()) { switch (ctx.getVersion().getVersion()) {
case DSTU2: { case DSTU2: {
FhirInstanceValidator instanceValidator; FhirInstanceValidator instanceValidator;
ValidationSupportChain validationSupportChain = validationSupportChain =
ValidationSupportChainCreator.getValidationSupportChainDstu2(ctx, theCommandLine); ValidationSupportChainCreator.getValidationSupportChainDstu2(ctx, theCommandLine);
validationSupportChain.oneCodingIsSufficient = true;
instanceValidator = new FhirInstanceValidator(validationSupportChain); instanceValidator = new FhirInstanceValidator(validationSupportChain);
val.registerValidatorModule(instanceValidator); val.registerValidatorModule(instanceValidator);
break; break;
} }
case DSTU3: case DSTU3:
case R4: { case R4: {
ourLog.info("Adding FHIR R4 validation support chain");
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx); FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ctx);
val.registerValidatorModule(instanceValidator); val.registerValidatorModule(instanceValidator);
ValidationSupportChain validationSupportChain = validationSupportChain =
ValidationSupportChainCreator.getValidationSupportChainR4(ctx, theCommandLine); ValidationSupportChainCreator.getValidationSupportChainR4(ctx, theCommandLine);
instanceValidator.setValidationSupport(validationSupportChain); instanceValidator.setValidationSupport(validationSupportChain);
break; break;
@ -171,6 +177,25 @@ public class ValidateCommand extends BaseCommand {
throw new ParseException( throw new ParseException(
Msg.code(1620) + "Profile validation (-p) is not supported for this FHIR version"); Msg.code(1620) + "Profile validation (-p) is not supported for this FHIR version");
} }
// Do we allow CodeableConcepts to be satisfied by only one coding?
// Useful when we are using additionalBindings.
validationSupportChain.oneCodingIsSufficient = theCommandLine.hasOption("oneCode");
// If they want to do profile validation - there might be a whole IG to load...
if (theCommandLine.hasOption("igpack")) {
String igPack = theCommandLine.getOptionValue("igpack");
ourLog.info("Loading IG Package from {} to the validation support chain", igPack);
try {
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(ctx);
npmPackageSupport.loadPackageFromFile(igPack);
validationSupportChain.addValidationSupport(0, npmPackageSupport);
} catch (IOException iox) {
throw new ParseException(
Msg.code(1620) + "IGpack validation (--igpack) failed: " + iox.getMessage());
}
ourLog.info("Completed loading IG Package from {}", igPack);
}
} }
val.setValidateAgainstStandardSchema(theCommandLine.hasOption("x")); val.setValidateAgainstStandardSchema(theCommandLine.hasOption("x"));

View File

@ -23,6 +23,7 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.i18n.Msg; import ca.uhn.fhir.i18n.Msg;
import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLine;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService;
import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport; import org.hl7.fhir.common.hapi.validation.support.InMemoryTerminologyServerValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.LocalFileValidationSupport; import org.hl7.fhir.common.hapi.validation.support.LocalFileValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
@ -52,7 +53,7 @@ public class ValidationSupportChainCreator {
if (commandLine.hasOption("r")) { if (commandLine.hasOption("r")) {
chain.addValidationSupport(new LoadingValidationSupportDstu3()); chain.addValidationSupport(new LoadingValidationSupportDstu3());
} }
chain.addValidationSupport(new CommonCodeSystemsTerminologyService(ctx));
return chain; return chain;
} }

View File

@ -63,7 +63,7 @@
<dependency> <dependency>
<groupId>jakarta.servlet</groupId> <groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId> <artifactId>jakarta.servlet-api</artifactId>
<scope>test</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>ca.uhn.hapi.fhir</groupId> <groupId>ca.uhn.hapi.fhir</groupId>

View File

@ -81,7 +81,7 @@ public class InstanceReindexServiceImplR5Test extends BaseJpaR5Test {
IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer")); IIdType id = createPatient(withFamily("Simpson"), withGiven("Homer"));
runInTransaction(this::logAllNonUniqueIndexes); runInTransaction((Runnable) this::logAllNonUniqueIndexes);
Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null); Parameters outcome = (Parameters) mySvc.reindexDryRun(new SystemRequestDetails(), id, null);
ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome)); ourLog.info("Output:{}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome));

View File

@ -133,7 +133,11 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -3,13 +3,14 @@ package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler; import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ClasspathUtil; import ca.uhn.fhir.util.ClasspathUtil;
import jakarta.annotation.Nonnull; import jakarta.annotation.Nonnull;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.NpmPackage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -31,11 +32,16 @@ public class NpmPackageValidationSupport extends PrePopulatedValidationSupport {
super(theFhirContext); super(theFhirContext);
} }
@Override
public String getName() {
return getFhirContext().getVersion().getVersion() + " NPM Package Validation Support";
}
/** /**
* Load an NPM package using a classpath specification, e.g. <code>/path/to/resource/my_package.tgz</code>. The * Load an NPM package using a classpath specification, e.g. <code>/path/to/resource/my_package.tgz</code>. The
* classpath spec can optionally be prefixed with the string <code>classpath:</code> * classpath spec can optionally be prefixed with the string <code>classpath:</code>
* *
* @throws InternalErrorException If the classpath file can't be found * @throws IOException If the classpath file can't be found
*/ */
public void loadPackageFromClasspath(String theClasspath) throws IOException { public void loadPackageFromClasspath(String theClasspath) throws IOException {
try (InputStream is = ClasspathUtil.loadResourceAsStream(theClasspath)) { try (InputStream is = ClasspathUtil.loadResourceAsStream(theClasspath)) {
@ -47,6 +53,21 @@ public class NpmPackageValidationSupport extends PrePopulatedValidationSupport {
} }
} }
/**
* Load an NPM package from the filesystem e.g. <code>my_package.tgz</code>.
*
* @throws IOException If the package file can't be found
*/
public void loadPackageFromFile(String theFile) throws IOException {
File inFile = new File(theFile);
InputStream is = new FileInputStream(inFile);
NpmPackage pkg = NpmPackage.fromPackage(is);
if (pkg.getFolders().containsKey("package")) {
loadResourcesFromPackage(pkg);
loadBinariesFromPackage(pkg);
}
}
private void loadResourcesFromPackage(NpmPackage thePackage) { private void loadResourcesFromPackage(NpmPackage thePackage) {
NpmPackage.NpmPackageFolder packageFolder = thePackage.getFolders().get("package"); NpmPackage.NpmPackageFolder packageFolder = thePackage.getFolders().get("package");

View File

@ -371,12 +371,13 @@ public class ValidationSupportChain implements IValidationSupport {
if (retVal != null) { if (retVal != null) {
if (ourLog.isDebugEnabled()) { if (ourLog.isDebugEnabled()) {
ourLog.debug( ourLog.debug(
"Code {}|{} '{}' in ValueSet {} validated by {}", "Code {}|{} '{}' in ValueSet {} validated by {} result: {}",
theCodeSystem, theCodeSystem,
theCode, theCode,
theDisplay, theDisplay,
theValueSetUrl, theValueSetUrl,
next.getName()); next.getName(),
retVal.getMessage() != null ? retVal.getMessage() : "Success");
} }
return retVal; return retVal;
} }
@ -401,12 +402,13 @@ public class ValidationSupportChain implements IValidationSupport {
if (retVal != null) { if (retVal != null) {
if (ourLog.isDebugEnabled()) { if (ourLog.isDebugEnabled()) {
ourLog.debug( ourLog.debug(
"Code {}|{} '{}' in ValueSet {} validated by {}", "Code {}|{} '{}' in ValueSet {} validated by {} result: {}",
theCodeSystem, theCodeSystem,
theCode, theCode,
theDisplay, theDisplay,
theValueSet.getIdElement(), theValueSet.getIdElement(),
next.getName()); next.getName(),
retVal.getMessage() != null ? retVal.getMessage() : "Success");
} }
return retVal; return retVal;
} }
@ -445,4 +447,18 @@ public class ValidationSupportChain implements IValidationSupport {
} }
return null; return null;
} }
/**
* See VersionSpecificWorkerContextWrapper#validateCode in hapi-fhir-validation.
* <p>
* If true, validation for codings will return a positive result if AT LEAST ONE coding is valid.
* If false, validation for codings will return a positive result if there is ALL codings are valid.
* </p>
* @return if the application has configured validation to use logical AND, as opposed to logical OR, which is the default
*/
public boolean oneCodingIsSufficient = false; // default value from parent Interface (it's the wrong way round !!!)
@Override
public boolean isEnabledValidationForCodingsLogicalAnd() { // should be named ....LogicalAny
return oneCodingIsSufficient;
}
} }

View File

@ -1050,9 +1050,9 @@
<!-- Site properties --> <!-- Site properties -->
<fontawesomeVersion>5.4.1</fontawesomeVersion> <fontawesomeVersion>5.4.1</fontawesomeVersion>
<maven.compiler.source>11</maven.compiler.source> <maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target> <maven.compiler.target>17</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release> <maven.compiler.release>17</maven.compiler.release>
<maven.compiler.testSource>17</maven.compiler.testSource> <maven.compiler.testSource>17</maven.compiler.testSource>
<maven.compiler.testTarget>17</maven.compiler.testTarget> <maven.compiler.testTarget>17</maven.compiler.testTarget>
<maven.compiler.testRelease>17</maven.compiler.testRelease> <maven.compiler.testRelease>17</maven.compiler.testRelease>
@ -3299,4 +3299,3 @@
</profile> </profile>
</profiles> </profiles>
</project> </project>