Add NPM validation support module (#2782)

* Add NPM validation support module

* Add changelog
This commit is contained in:
James Agnew 2021-07-07 16:28:41 -04:00 committed by GitHub
parent 3e7bc2a81e
commit 95306c3d64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 283 additions and 25 deletions

View File

@ -56,18 +56,25 @@ public class ClasspathUtil {
/** /**
* Load a classpath resource, throw an {@link InternalErrorException} if not found * Load a classpath resource, throw an {@link InternalErrorException} if not found
*
* @throws InternalErrorException If the resource can't be found
*/ */
@Nonnull @Nonnull
public static InputStream loadResourceAsStream(String theClasspath) { public static InputStream loadResourceAsStream(String theClasspath) {
InputStream retVal = ClasspathUtil.class.getResourceAsStream(theClasspath); String classpath = theClasspath;
if (classpath.startsWith("classpath:")) {
classpath = classpath.substring("classpath:".length());
}
InputStream retVal = ClasspathUtil.class.getResourceAsStream(classpath);
if (retVal == null) { if (retVal == null) {
if (theClasspath.startsWith("/")) { if (classpath.startsWith("/")) {
retVal = ClasspathUtil.class.getResourceAsStream(theClasspath.substring(1)); retVal = ClasspathUtil.class.getResourceAsStream(classpath.substring(1));
} else { } else {
retVal = ClasspathUtil.class.getResourceAsStream("/" + theClasspath); retVal = ClasspathUtil.class.getResourceAsStream("/" + classpath);
} }
if (retVal == null) { if (retVal == null) {
throw new InternalErrorException("Unable to find classpath resource: " + theClasspath); throw new InternalErrorException("Unable to find classpath resource: " + classpath);
} }
} }
return retVal; return retVal;

View File

@ -40,8 +40,10 @@ import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.CommonCodeSystemsTerminologyService; 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.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport; import org.hl7.fhir.common.hapi.validation.support.PrePopulatedValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport; import org.hl7.fhir.common.hapi.validation.support.RemoteTerminologyServiceValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
@ -437,5 +439,45 @@ public class ValidatorExamples {
// END SNIPPET: validateFiles // END SNIPPET: validateFiles
} }
@SuppressWarnings("unused")
private static void npm() throws Exception {
// START SNIPPET: npm
// Create an NPM Package Support module and load one package in from
// the classpath
FhirContext ctx = FhirContext.forR4();
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(ctx);
npmPackageSupport.loadPackageFromClasspath("classpath:package/UK.Core.r4-1.1.0.tgz");
// Create a support chain including the NPM Package Support
ValidationSupportChain validationSupportChain = new ValidationSupportChain(
npmPackageSupport,
new DefaultProfileValidationSupport(ctx),
new CommonCodeSystemsTerminologyService(ctx),
new InMemoryTerminologyServerValidationSupport(ctx),
new SnapshotGeneratingValidationSupport(ctx)
);
CachingValidationSupport validationSupport = new CachingValidationSupport(validationSupportChain);
// Create a validator. Note that for good performance you can create as many validator objects
// as you like, but you should reuse the same validation support object in all of the,.
FhirValidator validator = ctx.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupport);
validator.registerValidatorModule(instanceValidator);
// Create a test patient to validate
Patient patient = new Patient();
patient.getMeta().addProfile("https://fhir.nhs.uk/R4/StructureDefinition/UKCore-Patient");
// System but not value set for NHS identifier (this should generate an error)
patient.addIdentifier().setSystem("https://fhir.nhs.uk/Id/nhs-number");
// Perform the validation
ValidationResult outcome = validator.validateWithResult(patient);
// END SNIPPET: npm
}
} }

View File

@ -0,0 +1,5 @@
---
type: add
issue: 2782
title: "A new Validation Support Module has been added that can use NPM Packages to supply
validation conformance artifacts programatcally to the validator."

View File

@ -59,6 +59,23 @@ to validate the resource. It will not work unless you include the
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|instanceValidator}} {{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|instanceValidator}}
``` ```
<a name="packages"/>
# Validating Using Packages
HAPI FHIR supports the use of FHIR NPM Packages for supplying validation artifacts.
When using the HAPI FHIR [JPA Server](../server_jpa/) you can simply upload your packages into the JPA Server package registry and the contents will be made available to the validator.
If you are using the validator as a standalone service (i.e. you are invoking it via a Java call) you will need to explcitly make your packages available to the validation support chain.
The following example shows the use of [NpmPackageValidationSupport](./validation_support_modules.html#npmpackagevalidationsupport) to load a package and use it to validate a resource.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|npm}}
```
<a name="migrating-to-5x"></a> <a name="migrating-to-5x"></a>
# Migrating to HAPI FHIR 5.x # Migrating to HAPI FHIR 5.x

View File

@ -12,43 +12,53 @@ There are a several implementations of the [IValidationSupport](/hapi-fhir/apido
# ValidationSupportChain # ValidationSupportChain
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java)
This module can be used to combine multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. Note that nearly all methods in the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface are permitted to return `null` if they are not able to service a particular method call. So for example, if a call to the [`validateCode`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html#validateCode(ca.uhn.fhir.context.support.ValidationSupportContext,ca.uhn.fhir.context.support.ConceptValidationOptions,java.lang.String,java.lang.String,java.lang.String,java.lang.String)) method is made, the validator will try each module in the chain until one of them returns a non-null response. This module can be used to combine multiple implementations together so that for every request, each support class instance in the chain is tried in sequence. Note that nearly all methods in the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html) interface are permitted to return `null` if they are not able to service a particular method call. So for example, if a call to the [`validateCode`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/context/support/IValidationSupport.html#validateCode(ca.uhn.fhir.context.support.ValidationSupportContext,ca.uhn.fhir.context.support.ConceptValidationOptions,java.lang.String,java.lang.String,java.lang.String,java.lang.String)) method is made, the validator will try each module in the chain until one of them returns a non-null response.
# DefaultProfileValidationSupport # DefaultProfileValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-base/undefined/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.html) / [Source](https://github.com/hapifhir/hapi-fhir/blob/master/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/DefaultProfileValidationSupport.java)
This module supplies the built-in FHIR core structure definitions, including both FHIR resource definitions (StructureDefinition resources) and FHIR built-in vocabulary (ValueSet and CodeSystem resources). This module supplies the built-in FHIR core structure definitions, including both FHIR resource definitions (StructureDefinition resources) and FHIR built-in vocabulary (ValueSet and CodeSystem resources).
# InMemoryTerminologyServerValidationSupport # InMemoryTerminologyServerValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/InMemoryTerminologyServerValidationSupport.java)
This module acts as a simple terminology service that can validate codes against ValueSet and CodeSystem resources purely in-memory (i.e. with no database). This is sufficient in many basic cases, although it is not able to validate CodeSystems with external content (i.e CodeSystems where the `CodeSystem.content` field is `external`, such as the LOINC and SNOMED CT CodeSystems). This module acts as a simple terminology service that can validate codes against ValueSet and CodeSystem resources purely in-memory (i.e. with no database). This is sufficient in many basic cases, although it is not able to validate CodeSystems with external content (i.e CodeSystems where the `CodeSystem.content` field is `external`, such as the LOINC and SNOMED CT CodeSystems).
# PrePopulatedValidationSupport # PrePopulatedValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java)
This module contains a series of HashMaps that store loaded conformance resources in memory. Typically this is initialized at startup in order to add custom conformance resources into the chain. This module contains a series of HashMaps that store loaded conformance resources in memory. Typically this is initialized at startup in order to add custom conformance resources into the chain.
<a name="npmpackagevalidationsupport"/>
# NpmPackageValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.java)
This module can be used to load FHIR NPM Packages and supply the conformance resources within them to the validator. See [Validating Using Packages](./instance_validator.html#packages) for am example of how to use this module.
# CachingValidationSupport # CachingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java)
This module caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk, but it also has value even for purely in-memory validation since validating codes against a ValueSet can require the expansion of that ValueSet. This module caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk, but it also has value even for purely in-memory validation since validating codes against a ValueSet can require the expansion of that ValueSet.
# SnapshotGeneratingValidationSupport # SnapshotGeneratingValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/SnapshotGeneratingValidationSupport.java)
This module generates StructureDefinition snapshots as needed. This should be added to your chain if you are working wiith differential StructureDefinitions that do not include the snapshot view. This module generates StructureDefinition snapshots as needed. This should be added to your chain if you are working wiith differential StructureDefinitions that do not include the snapshot view.
# CommonCodeSystemsTerminologyService # CommonCodeSystemsTerminologyService
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CommonCodeSystemsTerminologyService.java)
This module validates codes in CodeSystems that are not distributed with the FHIR specification because they are difficult to distribute but are commonly used in FHIR resources. This module validates codes in CodeSystems that are not distributed with the FHIR specification because they are difficult to distribute but are commonly used in FHIR resources.
@ -132,7 +142,7 @@ The following table lists vocabulary that is validated by this module:
# RemoteTerminologyServiceValidationSupport # RemoteTerminologyServiceValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/RemoteTerminologyServiceValidationSupport.java)
This module validates codes using a remote FHIR-based terminology server. This module validates codes using a remote FHIR-based terminology server.
@ -145,7 +155,7 @@ This module will invoke the following operations on the remote terminology serve
# UnknownCodeSystemWarningValidationSupport # UnknownCodeSystemWarningValidationSupport
[JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/ja_20200218_validation_api_changes/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java) [JavaDoc](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.html) / [Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/UnknownCodeSystemWarningValidationSupport.java)
This validation support module may be placed at the end of a ValidationSupportChain in order to configure the validator to generate a warning if a resource being validated contains an unknown code system. This validation support module may be placed at the end of a ValidationSupportChain in order to configure the validator to generate a warning if a resource being validated contains an unknown code system.

View File

@ -0,0 +1,55 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.util.ClasspathUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.utilities.npm.NpmPackage;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
/**
* This interceptor loads and parses FHIR NPM Conformance Packages, and makes the
* artifacts foudn within them available to the FHIR validator.
*
* @since 5.5.0
*/
public class NpmPackageValidationSupport extends PrePopulatedValidationSupport {
/**
* Constructor
*/
public NpmPackageValidationSupport(@Nonnull FhirContext theFhirContext) {
super(theFhirContext);
}
/**
* 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>
*
* @throws InternalErrorException If the classpath file can't be found
*/
public void loadPackageFromClasspath(String theClasspath) throws IOException {
try (InputStream is = ClasspathUtil.loadResourceAsStream(theClasspath)) {
NpmPackage pkg = NpmPackage.fromPackage(is);
if (pkg.getFolders().containsKey("package")) {
NpmPackage.NpmPackageFolder packageFolder = pkg.getFolders().get("package");
for (String nextFile : packageFolder.listFiles()) {
if (nextFile.toLowerCase(Locale.US).endsWith(".json")) {
String input = new String(packageFolder.getContent().get(nextFile), StandardCharsets.UTF_8);
IBaseResource resource = getFhirContext().newJsonParser().parseResource(input);
super.addResource(resource);
}
}
}
}
}
}

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet;
import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -26,7 +27,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/ */
public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport { public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport {
private final FhirContext myFhirContext;
private final Map<String, IBaseResource> myCodeSystems; private final Map<String, IBaseResource> myCodeSystems;
private final Map<String, IBaseResource> myStructureDefinitions; private final Map<String, IBaseResource> myStructureDefinitions;
private final Map<String, IBaseResource> myValueSets; private final Map<String, IBaseResource> myValueSets;
@ -55,7 +55,6 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null");
Validate.notNull(theValueSets, "theValueSets must not be null"); Validate.notNull(theValueSets, "theValueSets must not be null");
Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); Validate.notNull(theCodeSystems, "theCodeSystems must not be null");
myFhirContext = theFhirContext;
myStructureDefinitions = theStructureDefinitions; myStructureDefinitions = theStructureDefinitions;
myValueSets = theValueSets; myValueSets = theValueSets;
myCodeSystems = theCodeSystems; myCodeSystems = theCodeSystems;
@ -82,7 +81,7 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) { private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) {
Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null"); Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null");
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theCodeSystem); RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theCodeSystem);
String actualResourceName = resourceDef.getName(); String actualResourceName = resourceDef.getName();
Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName); Validate.isTrue(actualResourceName.equals(theResourceName), "the" + theResourceName + " must be a " + theResourceName + " - Got: " + actualResourceName);
@ -113,16 +112,16 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
addToMap(theStructureDefinition, myStructureDefinitions, url); addToMap(theStructureDefinition, myStructureDefinitions, url);
} }
private <T extends IBaseResource> void addToMap(T theStructureDefinition, Map<String, T> map, String theUrl) { private <T extends IBaseResource> void addToMap(T theResource, Map<String, T> theMap, String theUrl) {
if (isNotBlank(theUrl)) { if (isNotBlank(theUrl)) {
map.put(theUrl, theStructureDefinition); theMap.put(theUrl, theResource);
int lastSlashIdx = theUrl.lastIndexOf('/'); int lastSlashIdx = theUrl.lastIndexOf('/');
if (lastSlashIdx != -1) { if (lastSlashIdx != -1) {
map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition); theMap.put(theUrl.substring(lastSlashIdx + 1), theResource);
int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1); int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 1);
if (previousSlashIdx != -1) { if (previousSlashIdx != -1) {
map.put(theUrl.substring(previousSlashIdx + 1), theStructureDefinition); theMap.put(theUrl.substring(previousSlashIdx + 1), theResource);
} }
} }
@ -143,12 +142,33 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
* </ul> * </ul>
* </p> * </p>
*/ */
public void addValueSet(ValueSet theValueSet) { public void addValueSet(IBaseResource theValueSet) {
String url = processResourceAndReturnUrl(theValueSet, "ValueSet"); String url = processResourceAndReturnUrl(theValueSet, "ValueSet");
addToMap(theValueSet, myValueSets, url); addToMap(theValueSet, myValueSets, url);
} }
/**
* @param theResource The resource. This method delegates to the type-specific methods (e.g. {@link #addCodeSystem(IBaseResource)})
* and will do nothing if the resource type is not supported by this class.
* @since 5.5.0
*/
public void addResource(@Nonnull IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
switch (getFhirContext().getResourceType(theResource)) {
case "StructureDefinition":
addStructureDefinition(theResource);
break;
case "CodeSystem":
addCodeSystem(theResource);
break;
case "ValueSet":
addValueSet(theResource);
break;
}
}
@Override @Override
public List<IBaseResource> fetchAllConformanceResources() { public List<IBaseResource> fetchAllConformanceResources() {
ArrayList<IBaseResource> retVal = new ArrayList<>(); ArrayList<IBaseResource> retVal = new ArrayList<>();
@ -159,7 +179,7 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
} }
@Override @Override
public List<IBaseResource> fetchAllStructureDefinitions() { public <T extends IBaseResource> List<T> fetchAllStructureDefinitions() {
return toList(myStructureDefinitions); return toList(myStructureDefinitions);
} }
@ -187,5 +207,4 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) { public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
return myValueSets.containsKey(theValueSetUrl); return myValueSets.containsKey(theValueSetUrl);
} }
} }

View File

@ -0,0 +1,36 @@
package org.hl7.fhir.common.hapi.validation.support;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertSame;
public class PrePopulatedValidationSupportTest {
private final PrePopulatedValidationSupport mySvc = new PrePopulatedValidationSupport(FhirContext.forR4Cached());
@Test
public void testAddResource() {
CodeSystem cs = new CodeSystem();
cs.setUrl("http://cs");
mySvc.addResource(cs);
ValueSet vs = new ValueSet();
vs.setUrl("http://vs");
mySvc.addResource(vs);
StructureDefinition sd = new StructureDefinition();
sd.setUrl("http://sd");
mySvc.addResource(sd);
assertSame(cs, mySvc.fetchCodeSystem("http://cs"));
assertSame(vs, mySvc.fetchValueSet("http://vs"));
assertSame(sd, mySvc.fetchStructureDefinition("http://sd"));
}
}

View File

@ -0,0 +1,67 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
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.NpmPackageValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationSupport;
import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
public class NpmPackageValidationSupportTest {
private static final Logger ourLog = LoggerFactory.getLogger(NpmPackageValidationSupportTest.class);
private FhirContext myFhirContext = FhirContext.forR4Cached();
@Test
public void testValidateWithPackage() throws IOException {
// Create an NPM Package Support module and load one package in from
// the classpath
NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirContext);
npmPackageSupport.loadPackageFromClasspath("classpath:package/UK.Core.r4-1.1.0.tgz");
// Create a support chain including the NPM Package Support
ValidationSupportChain validationSupportChain = new ValidationSupportChain(
npmPackageSupport,
new DefaultProfileValidationSupport(myFhirContext),
new CommonCodeSystemsTerminologyService(myFhirContext),
new InMemoryTerminologyServerValidationSupport(myFhirContext),
new SnapshotGeneratingValidationSupport(myFhirContext)
);
CachingValidationSupport validationSupport = new CachingValidationSupport(validationSupportChain);
// Create a validator
FhirValidator validator = myFhirContext.newValidator();
FhirInstanceValidator instanceValidator = new FhirInstanceValidator(validationSupport);
validator.registerValidatorModule(instanceValidator);
// Create a test patient to validate
Patient patient = new Patient();
patient.getMeta().addProfile("https://fhir.nhs.uk/R4/StructureDefinition/UKCore-Patient");
// System but not value set for NHS identifier (this should generate an error)
patient.addIdentifier().setSystem("https://fhir.nhs.uk/Id/nhs-number");
// Perform the validation
ValidationResult outcome = validator.validateWithResult(patient);
String outcomeSerialized = myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.toOperationOutcome());
ourLog.info(outcomeSerialized);
assertThat(outcomeSerialized, containsString("Patient.identifier:nhsNumber.value: minimum required = 1, but only found 0"));
}
}