diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java index 50f9e891d9c..2b5df22bc6d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/support/IValidationSupport.java @@ -255,6 +255,13 @@ public interface IValidationSupport { return null; } + /** + * Fetch the given binary data by key. + * @param binaryKey + * @return + */ + default byte[] fetchBinary(String binaryKey) { return null; } + /** * Validates that the given code exists and if possible returns a display * name. This method is called to check codes which are found in "example" diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/examples.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/examples.md index 79bdba4a59c..fe0d1dd8956 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/examples.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/examples.md @@ -6,8 +6,8 @@ The following code can be used to generate a Snapshot Profile (StructureDefinition) when all you have is a differential. ```java -// Create a validation chain that includes default validation support and a -// snapshot generator +// Create a validation support chain that includes default validation support +// and a snapshot generator DefaultProfileValidationSupport defaultSupport = new DefaultProfileValidationSupport(); SnapshotGeneratingValidationSupport snapshotGenerator = new SnapshotGeneratingValidationSupport(myFhirCtx, defaultSupport); ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapshotGenerator); @@ -15,5 +15,29 @@ ValidationSupportChain chain = new ValidationSupportChain(defaultSupport, snapsh // Generate the snapshot StructureDefinition snapshot = chain.generateSnapshot(differential, "http://foo", null, "THE BEST PROFILE"); ``` - +## Validate a Resource with Cross Version Extensions + +The following code can be used to validate a resource using FHIR [Cross Version Extensions](http://hl7.org/fhir/versions.html#extensions). + +Note that you must have the [hl7.fhir.xver-extensions-0.0.11.tgz](http://fhir.org/packages/hl7.fhir.xver-extensions/0.0.11/package.tgz) package available in your classpath. + +```java +// Create a validation support chain that includes default validation support +// and support from the hl7.fhir.xver-extensions NPM pacakage. +NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirCtx); +npmPackageSupport.loadPackageFromClasspath("classpath:package/hl7.fhir.xver-extensions-0.0.11.tgz"); + +myFhirCtx.setValidationSupport(new ValidationSupportChain( + new DefaultProfileValidationSupport(myFhirCtx), + npmPackageSupport + )); + +FhirInstanceValidator instanceValidator = new FhirInstanceValidator(myFhirCtx); + +FhirValidator validator = myFhirCtx.newValidator(); +validator.registerValidatorModule(instanceValidator); + +// Validate theResource +ValidationResult validationResult = validator.validateWithResult(theResource); +``` diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java index 5b5704a2d05..6eab27adbbb 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/BaseValidationSupportWrapper.java @@ -56,6 +56,11 @@ public class BaseValidationSupportWrapper extends BaseValidationSupport { return myWrap.fetchResource(theClass, theUri); } + @Override + public byte[] fetchBinary(String theBinaryKey) { + return myWrap.fetchBinary(theBinaryKey); + } + @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { return myWrap.isCodeSystemSupported(theValidationSupportContext, theSystem); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java index 3b99614b569..26bf41203ea 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupport.java @@ -47,7 +47,7 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple private final Cache myExpandValueSetCache; /** - * Constuctor with default timeouts + * Constructor with default timeouts * * @param theWrap The validation support module to wrap */ @@ -140,6 +140,11 @@ public class CachingValidationSupport extends BaseValidationSupportWrapper imple return loadFromCache(myCache, "fetchStructureDefinition " + theUrl, t -> super.fetchStructureDefinition(theUrl)); } + @Override + public byte[] fetchBinary(String theBinaryKey) { + return loadFromCache(myCache, "fetchBinary " + theBinaryKey, t -> super.fetchBinary(theBinaryKey)); + } + @Override public T fetchResource(@Nullable Class theClass, String theUri) { return loadFromCache(myCache, "fetchResource " + theClass + " " + theUri, 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 index 4db388eab2c..b36d2252340 100644 --- 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 @@ -4,13 +4,14 @@ 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.TextFile; 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.List; import java.util.Locale; /** @@ -38,18 +39,28 @@ public class NpmPackageValidationSupport extends PrePopulatedValidationSupport { 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); - } - } - + loadResourcesFromPackage(pkg); + loadBinariesFromPackage(pkg); } } } + private void loadResourcesFromPackage(NpmPackage thePackage) { + NpmPackage.NpmPackageFolder packageFolder = thePackage.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); + } + } + } + + private void loadBinariesFromPackage(NpmPackage thePackage) throws IOException { + List binaries = thePackage.list("other"); + for (String binaryName : binaries) { + addBinary(TextFile.streamToBytes(thePackage.load("other", binaryName)), binaryName); + } + } } 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 a54e170f438..fe88e97751e 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 @@ -34,15 +34,15 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS private final Map myCodeSystems; private final Map myStructureDefinitions; private final Map myValueSets; + private final Map myBinaries; /** * Constructor */ public PrePopulatedValidationSupport(FhirContext theContext) { - this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>()); + this(theContext, new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()); } - /** * Constructor * @@ -52,16 +52,49 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS * the resource itself. * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are * the resource itself. + **/ + public PrePopulatedValidationSupport( + FhirContext theFhirContext, + Map theStructureDefinitions, + Map theValueSets, + Map theCodeSystems) { + this(theFhirContext, theStructureDefinitions, theValueSets, theCodeSystems, new HashMap<>()); + } + + /** + * Constructor + * + * @param theStructureDefinitions The StructureDefinitions to be returned by this module. Keys are the logical URL for the resource, and + * values are the resource itself. + * @param theValueSets The ValueSets to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. + * @param theCodeSystems The CodeSystems to be returned by this module. Keys are the logical URL for the resource, and values are + * the resource itself. + * @param theBinaries The binary files to be returned by this module. Keys are the unique filename for the binary, and values + * are the contents of the file as a byte array. */ - public PrePopulatedValidationSupport(FhirContext theFhirContext, Map theStructureDefinitions, Map theValueSets, Map theCodeSystems) { + public PrePopulatedValidationSupport( + FhirContext theFhirContext, + Map theStructureDefinitions, + Map theValueSets, + Map theCodeSystems, + Map theBinaries) { super(theFhirContext); Validate.notNull(theFhirContext, "theFhirContext must not be null"); Validate.notNull(theStructureDefinitions, "theStructureDefinitions must not be null"); Validate.notNull(theValueSets, "theValueSets must not be null"); Validate.notNull(theCodeSystems, "theCodeSystems must not be null"); + Validate.notNull(theBinaries, "theBinaries must not be null"); myStructureDefinitions = theStructureDefinitions; myValueSets = theValueSets; myCodeSystems = theCodeSystems; + myBinaries = theBinaries; + } + + public void addBinary(byte[] theBinary, String theBinaryKey) { + Validate.notNull(theBinary, "theBinaryKey must not be null"); + Validate.notNull(theBinary, "the" + theBinaryKey + " must not be null"); + myBinaries.put(theBinaryKey, theBinary); } /** @@ -220,6 +253,9 @@ public class PrePopulatedValidationSupport extends BaseStaticResourceValidationS return myStructureDefinitions.get(theUrl); } + @Override + public byte[] fetchBinary(String theBinaryKey) { return myBinaries.get(theBinaryKey); } + @Override public boolean isCodeSystemSupported(ValidationSupportContext theValidationSupportContext, String theSystem) { return myCodeSystems.containsKey(theSystem); diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java index fe66719110d..f2dabe70cb0 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChain.java @@ -257,6 +257,17 @@ public class ValidationSupportChain implements IValidationSupport { return null; } + @Override + public byte[] fetchBinary(String key) { + for (IValidationSupport next : myChain) { + byte[] retVal = next.fetchBinary(key); + if (retVal != null) { + return retVal; + } + } + return null; + } + @Override public IBaseResource fetchStructureDefinition(String theUrl) { for (IValidationSupport next : myChain) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java index 356d4d3c55a..2f58495f05d 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapper.java @@ -131,12 +131,12 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo @Override public boolean hasBinaryKey(String s) { - throw new UnsupportedOperationException(Msg.code(2126)); + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s) != null; } @Override public byte[] getBinaryForKey(String s) { - throw new UnsupportedOperationException(Msg.code(2127)); + return myValidationSupportContext.getRootValidationSupport().fetchBinary(s); } @Override @@ -258,7 +258,7 @@ public class VersionSpecificWorkerContextWrapper extends I18nBase implements IWo @Override public void cacheResource(Resource res) { - throw new UnsupportedOperationException(Msg.code(660)); + } @Override diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java deleted file mode 100644 index d07d91916e1..00000000000 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/ValidationSupportChainTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.hl7.fhir.common.hapi.validation; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; -import ca.uhn.fhir.context.support.IValidationSupport; -import ca.uhn.fhir.i18n.Msg; -import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.mock; - -public class ValidationSupportChainTest { - - @Test - public void testVersionCheck() { - - DefaultProfileValidationSupport ctx3 = new DefaultProfileValidationSupport(FhirContext.forDstu3()); - DefaultProfileValidationSupport ctx4 = new DefaultProfileValidationSupport(FhirContext.forR4()); - - try { - new ValidationSupportChain(ctx3, ctx4); - } catch (ConfigurationException e) { - assertEquals(Msg.code(709) + "Trying to add validation support of version R4 to chain with 1 entries of version DSTU3", e.getMessage()); - } - - } - - @Test - public void testMissingContext() { - IValidationSupport ctx = mock(IValidationSupport.class); - try { - new ValidationSupportChain(ctx); - } catch (ConfigurationException e) { - assertEquals(Msg.code(708) + "Can not add validation support: getFhirContext() returns null", e.getMessage()); - } - } - - -} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupportTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupportTest.java index 87de9f373f7..782864acd9d 100644 --- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupportTest.java +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/CachingValidationSupportTest.java @@ -18,6 +18,8 @@ import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) @@ -68,5 +70,20 @@ public class CachingValidationSupportTest { assertEquals(1, responses.size()); } + @Test + public void fetchBinary_normally_accessesSuperOnlyOnce() { + final byte[] EXPECTED_BINARY = "dummyBinaryContent".getBytes(); + final String EXPECTED_BINARY_KEY = "dummyBinaryKey"; + when(myValidationSupport.getFhirContext()).thenReturn(ourCtx); + when(myValidationSupport.fetchBinary(EXPECTED_BINARY_KEY)).thenReturn(EXPECTED_BINARY); + CachingValidationSupport support = new CachingValidationSupport(myValidationSupport); + final byte[] firstActualBinary = support.fetchBinary(EXPECTED_BINARY_KEY); + assertEquals(EXPECTED_BINARY,firstActualBinary); + verify(myValidationSupport, times(1)).fetchBinary(EXPECTED_BINARY_KEY); + + final byte[] secondActualBinary = support.fetchBinary(EXPECTED_BINARY_KEY); + assertEquals(EXPECTED_BINARY,secondActualBinary); + verify(myValidationSupport, times(1)).fetchBinary(EXPECTED_BINARY_KEY); + } } 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 index aded98b4f1e..6297ae753eb 100644 --- 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 @@ -6,6 +6,9 @@ import org.hl7.fhir.r4.model.StructureDefinition; import org.hl7.fhir.r4.model.ValueSet; import org.junit.jupiter.api.Test; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertSame; public class PrePopulatedValidationSupportTest { @@ -33,4 +36,19 @@ public class PrePopulatedValidationSupportTest { } + @Test + public void testAddBinary() { + final Map EXPECTED_BINARIES_MAP = Map.of( + "dummyBinary1.txt", "myDummyContent1".getBytes(), + "dummyBinary2.txt", "myDummyContent2".getBytes() + ); + + for (Map.Entry entry : EXPECTED_BINARIES_MAP.entrySet()) { + mySvc.addBinary(entry.getValue(),entry.getKey()); + } + + for (Map.Entry entry : EXPECTED_BINARIES_MAP.entrySet()) { + assertArrayEquals(entry.getValue(), mySvc.fetchBinary(entry.getKey())); + } + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChainTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChainTest.java new file mode 100644 index 00000000000..59fd4ae9a84 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/support/ValidationSupportChainTest.java @@ -0,0 +1,81 @@ +package org.hl7.fhir.common.hapi.validation.support; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.i18n.Msg; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ValidationSupportChainTest { + + + @Test + public void testVersionCheck() { + + DefaultProfileValidationSupport ctx3 = new DefaultProfileValidationSupport(FhirContext.forDstu3()); + DefaultProfileValidationSupport ctx4 = new DefaultProfileValidationSupport(FhirContext.forR4()); + + try { + new ValidationSupportChain(ctx3, ctx4); + } catch (ConfigurationException e) { + assertEquals(Msg.code(709) + "Trying to add validation support of version R4 to chain with 1 entries of version DSTU3", e.getMessage()); + } + + } + + @Test + public void testMissingContext() { + IValidationSupport ctx = mock(IValidationSupport.class); + try { + new ValidationSupportChain(ctx); + } catch (ConfigurationException e) { + assertEquals(Msg.code(708) + "Can not add validation support: getFhirContext() returns null", e.getMessage()); + } + } + + @Test + public void fetchBinary_normally_returnsExpectedBinaries() { + + final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes(); + final byte[] EXPECTED_BINARY_CONTENT_2 = "dummyBinaryContent2".getBytes(); + final String EXPECTED_BINARY_KEY_1 = "dummyBinaryKey1"; + final String EXPECTED_BINARY_KEY_2 = "dummyBinaryKey2"; + + IValidationSupport validationSupport1 = createMockValidationSupportWithSingleBinary(EXPECTED_BINARY_KEY_1, EXPECTED_BINARY_CONTENT_1); + IValidationSupport validationSupport2 = createMockValidationSupportWithSingleBinary(EXPECTED_BINARY_KEY_2, EXPECTED_BINARY_CONTENT_2); + + ValidationSupportChain validationSupportChain = new ValidationSupportChain(validationSupport1, validationSupport2); + + final byte[] actualBinaryContent1 = validationSupportChain.fetchBinary(EXPECTED_BINARY_KEY_1 ); + final byte[] actualBinaryContent2 = validationSupportChain.fetchBinary(EXPECTED_BINARY_KEY_2 ); + + assertArrayEquals(EXPECTED_BINARY_CONTENT_1, actualBinaryContent1); + assertArrayEquals(EXPECTED_BINARY_CONTENT_2, actualBinaryContent2); + assertNull(validationSupportChain.fetchBinary("nonExistentKey")); + } + + + private static IValidationSupport createMockValidationSupport() { + IValidationSupport validationSupport; + validationSupport = mock(IValidationSupport.class); + FhirContext mockContext = mock(FhirContext.class); + when(mockContext.getVersion()).thenReturn(FhirVersionEnum.R4.getVersionImplementation()); + when(validationSupport.getFhirContext()).thenReturn(mockContext); + return validationSupport; + } + + + private static IValidationSupport createMockValidationSupportWithSingleBinary(String expected_binary_key, byte[] expected_binary_content) { + IValidationSupport validationSupport1 = createMockValidationSupport(); + when(validationSupport1.fetchBinary(expected_binary_key)).thenReturn(expected_binary_content); + return validationSupport1; + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java new file mode 100644 index 00000000000..e4b94d1dbd0 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/common/hapi/validation/validator/VersionSpecificWorkerContextWrapperTest.java @@ -0,0 +1,97 @@ +package org.hl7.fhir.common.hapi.validation.validator; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.context.support.ValidationSupportContext; +import ca.uhn.fhir.i18n.HapiLocalizer; +import org.hl7.fhir.r5.model.Resource; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class VersionSpecificWorkerContextWrapperTest { + + final byte[] EXPECTED_BINARY_CONTENT_1 = "dummyBinaryContent1".getBytes(); + final byte[] EXPECTED_BINARY_CONTENT_2 = "dummyBinaryContent2".getBytes(); + final String EXPECTED_BINARY_KEY_1 = "dummyBinaryKey1"; + final String EXPECTED_BINARY_KEY_2 = "dummyBinaryKey2"; + final String NON_EXISTENT_BINARY_KEY = "nonExistentBinaryKey"; + + @Test + public void hasBinaryKey_normally_returnsExpected() { + + IValidationSupport validationSupport = mockValidationSupportWithTwoBinaries(); + + ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); + VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class); + + VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter); + + assertTrue(wrapper.hasBinaryKey(EXPECTED_BINARY_KEY_1), "wrapper should have binary key " + EXPECTED_BINARY_KEY_1 ); + assertTrue(wrapper.hasBinaryKey(EXPECTED_BINARY_KEY_2), "wrapper should have binary key " + EXPECTED_BINARY_KEY_1 ); + assertFalse(wrapper.hasBinaryKey(NON_EXISTENT_BINARY_KEY), "wrapper should not have binary key " + NON_EXISTENT_BINARY_KEY); + + } + + @Test + public void getBinaryForKey_normally_returnsExpected() { + + IValidationSupport validationSupport = mockValidationSupportWithTwoBinaries(); + + ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); + VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class); + + VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter); + + assertArrayEquals(EXPECTED_BINARY_CONTENT_1, wrapper.getBinaryForKey(EXPECTED_BINARY_KEY_1)); + assertArrayEquals(EXPECTED_BINARY_CONTENT_2, wrapper.getBinaryForKey(EXPECTED_BINARY_KEY_2)); + assertNull(wrapper.getBinaryForKey(NON_EXISTENT_BINARY_KEY), "wrapper should return null for binary key " + NON_EXISTENT_BINARY_KEY); + } + + @Test + public void cacheResource_normally_executesWithoutException() { + + IValidationSupport validationSupport = mockValidationSupport(); + + ValidationSupportContext mockContext = mockValidationSupportContext(validationSupport); + VersionSpecificWorkerContextWrapper.IVersionTypeConverter converter = mock(VersionSpecificWorkerContextWrapper.IVersionTypeConverter.class); + + VersionSpecificWorkerContextWrapper wrapper = new VersionSpecificWorkerContextWrapper(mockContext, converter); + + wrapper.cacheResource(mock(Resource.class)); + } + + private IValidationSupport mockValidationSupportWithTwoBinaries() { + IValidationSupport validationSupport; + validationSupport = mockValidationSupport(); + when(validationSupport.fetchBinary(EXPECTED_BINARY_KEY_1)).thenReturn(EXPECTED_BINARY_CONTENT_1); + when(validationSupport.fetchBinary(EXPECTED_BINARY_KEY_2)).thenReturn(EXPECTED_BINARY_CONTENT_2); + return validationSupport; + } + + + private static ValidationSupportContext mockValidationSupportContext(IValidationSupport validationSupport) { + ValidationSupportContext mockContext; + mockContext = mock(ValidationSupportContext.class); + when(mockContext.getRootValidationSupport()).thenReturn(validationSupport); + return mockContext; + } + + + private static IValidationSupport mockValidationSupport() { + IValidationSupport mockValidationSupport; + mockValidationSupport = mock(IValidationSupport.class); + FhirContext mockFhirContext = mock(FhirContext.class); + when(mockFhirContext.getLocalizer()).thenReturn(new HapiLocalizer()); + when(mockFhirContext.getVersion()).thenReturn(FhirVersionEnum.R4.getVersionImplementation()); + when(mockValidationSupport.getFhirContext()).thenReturn(mockFhirContext); + return mockValidationSupport; + } +} 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 index 93b9cd4c11a..3caade7234e 100644 --- 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 @@ -12,27 +12,34 @@ import org.hl7.fhir.common.hapi.validation.support.SnapshotGeneratingValidationS 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.jetbrains.annotations.NotNull; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; +import java.util.Map; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; public class NpmPackageValidationSupportTest { private static final Logger ourLog = LoggerFactory.getLogger(NpmPackageValidationSupportTest.class); private FhirContext myFhirContext = FhirContext.forR4Cached(); + private Map EXPECTED_BINARIES_MAP = Map.of( + "dummyBinary1.txt", "myDummyContent1".getBytes(), + "dummyBinary2.txt", "myDummyContent2".getBytes() + ); @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"); + NpmPackageValidationSupport npmPackageSupport = getNpmPackageValidationSupport("classpath:package/UK.Core.r4-1.1.0.tgz"); // Create a support chain including the NPM Package Support ValidationSupportChain validationSupportChain = new ValidationSupportChain( @@ -64,4 +71,21 @@ public class NpmPackageValidationSupportTest { } + @NotNull + private NpmPackageValidationSupport getNpmPackageValidationSupport(String theClasspath) throws IOException { + NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(myFhirContext); + npmPackageSupport.loadPackageFromClasspath(theClasspath); + return npmPackageSupport; + } + + @Test + public void loadPackageFromClasspath_normally_loadsExpectedBinaries() throws IOException { + NpmPackageValidationSupport npmPackageSupport = getNpmPackageValidationSupport("classpath:package/dummy-package-with-binaries.tgz"); + + for (Map.Entry entry : EXPECTED_BINARIES_MAP.entrySet()) { + byte[] expectedBytes = entry.getValue(); + byte[] actualBytes = npmPackageSupport.fetchBinary(entry.getKey()); + assertArrayEquals(expectedBytes, actualBytes); + } + } } diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ParserWithValidationR4Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ParserWithValidationR4Test.java new file mode 100644 index 00000000000..5089809f277 --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/ParserWithValidationR4Test.java @@ -0,0 +1,35 @@ +package org.hl7.fhir.r4.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.common.hapi.validation.support.ValidationSupportChain; +import org.hl7.fhir.common.hapi.validation.validator.FhirInstanceValidator; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.junit.jupiter.api.Test; + +import static ca.uhn.fhir.util.ClasspathUtil.loadResource; + +public class ParserWithValidationR4Test { + private static final FhirContext ourCtx = FhirContext.forR4(); + + @Test + public void testActivityDefinitionElementsOrder() { + ourCtx.setValidationSupport(getValidationSupport()); + MedicationRequest med_req = ourCtx.newJsonParser().parseResource(MedicationRequest.class, loadResource("/r4/amz/medication-request-amz.json")); + + final FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ourCtx); + instanceValidator.setNoTerminologyChecks(true); + FhirValidator validator = ourCtx.newValidator(); + + validator.registerValidatorModule(instanceValidator); + ValidationResult validationResult = validator.validateWithResult(med_req); + validationResult.getMessages().forEach(System.out::println); + } + + private IValidationSupport getValidationSupport() { + return new ValidationSupportChain(new DefaultProfileValidationSupport(ourCtx)); + } +} diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/XverExtensionsValidationTest.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/XverExtensionsValidationTest.java new file mode 100644 index 00000000000..5ec2b42925f --- /dev/null +++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r4/validation/XverExtensionsValidationTest.java @@ -0,0 +1,99 @@ +package org.hl7.fhir.r4.validation; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; +import ca.uhn.fhir.context.support.IValidationSupport; +import ca.uhn.fhir.validation.FhirValidator; +import ca.uhn.fhir.validation.ResultSeverityEnum; +import ca.uhn.fhir.validation.SingleValidationMessage; +import ca.uhn.fhir.validation.ValidationResult; +import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport; +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.validator.FhirInstanceValidator; +import org.hl7.fhir.r4.model.IntegerType; +import org.hl7.fhir.r4.model.MedicationRequest; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.function.Predicate; + +import static ca.uhn.fhir.util.ClasspathUtil.loadResource; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class XverExtensionsValidationTest { + private static final FhirContext ourCtx = FhirContext.forR4(); + + @Test + public void validation_XverExtensionsAndCachingValidationSupport_ReturnsNoErrors() throws IOException { + ourCtx.setValidationSupport(new CachingValidationSupport(getValidationSupport())); + MedicationRequest med_req; + med_req = getMedicationRequest(); + FhirValidator validator = getFhirValidator(); + + ValidationResult validationResult = validator.validateWithResult(med_req); + + assertEquals(0, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + } + + + @Test + public void validation_XverExtensions_ReturnsNoErrors() throws IOException { + ourCtx.setValidationSupport(getValidationSupport()); + MedicationRequest med_req = getMedicationRequest(); + FhirValidator validator = getFhirValidator(); + + ValidationResult validationResult = validator.validateWithResult(med_req); + + assertEquals(0, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + } + + @Test + public void validation_InvalidExtensionValue_ReturnsError() throws IOException { + ourCtx.setValidationSupport(getValidationSupport()); + MedicationRequest med_req = getMedicationRequest(); + med_req.getMeta().getExtension().get(0).setValue(new IntegerType().setValue(123)); + FhirValidator validator = getFhirValidator(); + + ValidationResult validationResult = validator.validateWithResult(med_req); + + assertEquals(1, validationResult.getMessages().stream().filter(errorMessagePredicate()).count()); + SingleValidationMessage errorMessage = validationResult.getMessages().stream().filter(errorMessagePredicate()).findFirst().get(); + assertEquals("Extension_EXT_Type", errorMessage.getMessageId()); + } + + @NotNull + private static Predicate errorMessagePredicate() { + return message -> message.getSeverity() == ResultSeverityEnum.ERROR; + } + + @NotNull + private static FhirValidator getFhirValidator() { + FhirValidator validator; + final FhirInstanceValidator instanceValidator = new FhirInstanceValidator(ourCtx); + instanceValidator.setNoTerminologyChecks(true); + validator = ourCtx.newValidator(); + + validator.registerValidatorModule(instanceValidator); + return validator; + } + + @NotNull + private static MedicationRequest getMedicationRequest() { + MedicationRequest med_req; + med_req = ourCtx.newJsonParser().parseResource(MedicationRequest.class, loadResource("/r4/amz/medication-request-amz.json")); + return med_req; + } + + @NotNull + private IValidationSupport getValidationSupport() throws IOException { + NpmPackageValidationSupport npmPackageSupport = new NpmPackageValidationSupport(ourCtx); + npmPackageSupport.loadPackageFromClasspath("classpath:package/hl7.fhir.xver-extensions-0.0.11.tgz"); + + return new ValidationSupportChain( + new DefaultProfileValidationSupport(ourCtx), + npmPackageSupport + ); + } +} diff --git a/hapi-fhir-validation/src/test/resources/package/dummy-package-with-binaries.tgz b/hapi-fhir-validation/src/test/resources/package/dummy-package-with-binaries.tgz new file mode 100644 index 00000000000..65c9c528081 Binary files /dev/null and b/hapi-fhir-validation/src/test/resources/package/dummy-package-with-binaries.tgz differ diff --git a/hapi-fhir-validation/src/test/resources/package/hl7.fhir.xver-extensions-0.0.11.tgz b/hapi-fhir-validation/src/test/resources/package/hl7.fhir.xver-extensions-0.0.11.tgz new file mode 100644 index 00000000000..bbd29805346 Binary files /dev/null and b/hapi-fhir-validation/src/test/resources/package/hl7.fhir.xver-extensions-0.0.11.tgz differ diff --git a/hapi-fhir-validation/src/test/resources/r4/amz/medication-request-amz.json b/hapi-fhir-validation/src/test/resources/r4/amz/medication-request-amz.json new file mode 100644 index 00000000000..4b68264b026 --- /dev/null +++ b/hapi-fhir-validation/src/test/resources/r4/amz/medication-request-amz.json @@ -0,0 +1,38 @@ +{ + "resourceType" : "MedicationRequest", + "meta" : { + "versionId" : "1", + "tag" : [ + { + "system" : "https://smarthealthit.org/tags", + "code" : "synthea-8-2017" + } + ], + "lastUpdated" : "2021-04-17T02:39:06.885-04:00", + "extension" : [ + { + "valueUri" : "https://r2.smarthealthit.org", + "url" : "http://hl7.org/fhir/4.0/StructureDefinition/extension-Meta.source" + } + ] + }, + "encounter" : { + "reference" : "Encounter/a83e5350-6462-4331-adba-dc72552cecbd" + }, + "subject" : { + "reference" : "Patient/ab606cad-b848-4a24-b3b6-61075563fadf" + }, + "authoredOn" : "2018-02-19", + "status" : "stopped", + "medicationCodeableConcept" : { + "coding" : [ + { + "display" : "Atorvastatin", + "system" : "http://www.nlm.nih.gov/research/umls/rxnorm", + "code" : "259255" + } + ], + "text" : "Atorvastatin" + }, + "intent" : "order" +}