From 95306c3d64eafbf7cfc38cca0c45147b387e30f0 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Wed, 7 Jul 2021 16:28:41 -0400
Subject: [PATCH] Add NPM validation support module (#2782)
* Add NPM validation support module
* Add changelog
---
.../java/ca/uhn/fhir/util/ClasspathUtil.java | 17 +++--
.../uhn/hapi/fhir/docs/ValidatorExamples.java | 44 +++++++++++-
...782-add-npm-validation-support-module.yaml | 5 ++
.../docs/validation/instance_validator.md | 17 +++++
.../validation/validation_support_modules.md | 28 +++++---
.../support/NpmPackageValidationSupport.java | 55 ++++++++++++++
.../PrePopulatedValidationSupport.java | 39 +++++++---
.../PrePopulatedValidationSupportTest.java | 36 ++++++++++
.../NpmPackageValidationSupportTest.java | 67 ++++++++++++++++++
.../resources/package/UK.Core.r4-1.1.0.tgz | Bin 0 -> 28433 bytes
10 files changed, 283 insertions(+), 25 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_5_0/2782-add-npm-validation-support-module.yaml
create mode 100644 hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/NpmPackageValidationSupport.java
create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupportTest.java
create mode 100644 hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/NpmPackageValidationSupportTest.java
create mode 100644 hapi-fhir-validation/src/test/resources/package/UK.Core.r4-1.1.0.tgz
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 myCodeSystems;
private final Map myStructureDefinitions;
private final Map myValueSets;
@@ -55,7 +55,6 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null");
Validate.notNull(theValueSets, "theValueSets must not be null");
Validate.notNull(theCodeSystems, "theCodeSystems must not be null");
- myFhirContext = theFhirContext;
myStructureDefinitions = theStructureDefinitions;
myValueSets = theValueSets;
myCodeSystems = theCodeSystems;
@@ -82,7 +81,7 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
private String processResourceAndReturnUrl(IBaseResource theCodeSystem, String theResourceName) {
Validate.notNull(theCodeSystem, "the" + theResourceName + " must not be null");
- RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(theCodeSystem);
+ RuntimeResourceDefinition resourceDef = getFhirContext().getResourceDefinition(theCodeSystem);
String actualResourceName = resourceDef.getName();
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);
}
- private void addToMap(T theStructureDefinition, Map map, String theUrl) {
+ private void addToMap(T theResource, Map theMap, String theUrl) {
if (isNotBlank(theUrl)) {
- map.put(theUrl, theStructureDefinition);
+ theMap.put(theUrl, theResource);
int lastSlashIdx = theUrl.lastIndexOf('/');
if (lastSlashIdx != -1) {
- map.put(theUrl.substring(lastSlashIdx + 1), theStructureDefinition);
+ theMap.put(theUrl.substring(lastSlashIdx + 1), theResource);
int previousSlashIdx = theUrl.lastIndexOf('/', lastSlashIdx - 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
*
*
*/
- public void addValueSet(ValueSet theValueSet) {
+ public void addValueSet(IBaseResource theValueSet) {
String url = processResourceAndReturnUrl(theValueSet, "ValueSet");
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
public List fetchAllConformanceResources() {
ArrayList retVal = new ArrayList<>();
@@ -159,7 +179,7 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
}
@Override
- public List fetchAllStructureDefinitions() {
+ public List fetchAllStructureDefinitions() {
return toList(myStructureDefinitions);
}
@@ -187,5 +207,4 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS
public boolean isValueSetSupported(ValidationSupportContext theValidationSupportContext, String theValueSetUrl) {
return myValueSets.containsKey(theValueSetUrl);
}
-
}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupportTest.java
new file mode 100644
index 00000000000..aded98b4f1e
--- /dev/null
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/PrePopulatedValidationSupportTest.java
@@ -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"));
+
+ }
+
+}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/NpmPackageValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/NpmPackageValidationSupportTest.java
new file mode 100644
index 00000000000..93b9cd4c11a
--- /dev/null
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/NpmPackageValidationSupportTest.java
@@ -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"));
+
+ }
+
+}
diff --git a/hapi-fhir-validation/src/test/resources/package/UK.Core.r4-1.1.0.tgz b/hapi-fhir-validation/src/test/resources/package/UK.Core.r4-1.1.0.tgz
new file mode 100644
index 0000000000000000000000000000000000000000..3f8bba35ea08d4d8dde56e6e6716d155c9b50d06
GIT binary patch
literal 28433
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
literal 0
HcmV?d00001