diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
index 6c1def2fdef..14d8e3dcc57 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ClasspathUtil.java
@@ -56,18 +56,25 @@ public class ClasspathUtil {
/**
* Load a classpath resource, throw an {@link InternalErrorException} if not found
+ *
+ * @throws InternalErrorException If the resource can't be found
*/
@Nonnull
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 (theClasspath.startsWith("/")) {
- retVal = ClasspathUtil.class.getResourceAsStream(theClasspath.substring(1));
+ if (classpath.startsWith("/")) {
+ retVal = ClasspathUtil.class.getResourceAsStream(classpath.substring(1));
} else {
- retVal = ClasspathUtil.class.getResourceAsStream("/" + theClasspath);
+ retVal = ClasspathUtil.class.getResourceAsStream("/" + classpath);
}
if (retVal == null) {
- throw new InternalErrorException("Unable to find classpath resource: " + theClasspath);
+ throw new InternalErrorException("Unable to find classpath resource: " + classpath);
}
}
return retVal;
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java
index 2b4eef04aad..4c12593024c 100644
--- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ValidatorExamples.java
@@ -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.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.PrePopulatedValidationSupport;
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.instance.model.api.IBaseResource;
import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator;
@@ -437,5 +439,45 @@ public class ValidatorExamples {
// 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
+ }
+
+
+
+
}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2782-add-npm-validation-support-module.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2782-add-npm-validation-support-module.yaml
new file mode 100644
index 00000000000..3bbebbe094b
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2782-add-npm-validation-support-module.yaml
@@ -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."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md
index 3939318f427..ec17ebfdf9e 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/instance_validator.md
@@ -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}}
```
+
+
+# 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}}
+```
+
+
# Migrating to HAPI FHIR 5.x
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
index 5355fe98074..05da89a6993 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/validation_support_modules.md
@@ -12,43 +12,53 @@ There are a several implementations of the [IValidationSupport](/hapi-fhir/apido
# 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.
# 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).
# 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).
# 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.
+
+
+
+# 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
-[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.
# 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.
# 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.
@@ -132,7 +142,7 @@ The following table lists vocabulary that is validated by this module:
# 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.
@@ -145,7 +155,7 @@ This module will invoke the following operations on the remote terminology serve
# 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.
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.java
new file mode 100644
index 00000000000..4db388eab2c
--- /dev/null
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.java
@@ -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. /path/to/resource/my_package.tgz
. The
+ * classpath spec can optionally be prefixed with the string classpath:
+ *
+ * @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);
+ }
+ }
+
+ }
+ }
+ }
+
+}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java
index f74dca0f658..4941b497dc1 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupport.java
@@ -12,6 +12,7 @@ import org.hl7.fhir.r4.model.CodeSystem;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.ValueSet;
+import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -26,7 +27,6 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
*/
public class PrePopulatedValidationSupport extends BaseStaticResourceValidationSupport implements IValidationSupport {
- private final FhirContext myFhirContext;
private final Map