FHIR Chat · Example: Custom profile validation · hapi

Stream: hapi

Topic: Example: Custom profile validation


view this post on Zulip Johanna Eicher (Sep 19 2019 at 06:19):

Hi all,

could anyone be so nice to provide me with (or point me towards) an example of how to implement IValidationSupport when
- Structure definitions, code systems and value sets are stored locally on the file system
- Structure definitions are available as differentials only.

I do not have a FHIRStore, I only want to generate FHIR resources via Hapi and then validate them against custom profiles.

My main problem lies in the method "generateSnapshot" and how it relates to the other methods.

I have managed to get the validator working on snapshots (provided via the file system), but now I have the requirement to supply differentials only (which means - afaik - that I have to implement the generateSnapshot method).

I'm working with the following dependencies:

    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-structures-r4</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-validation</artifactId>
        <version>4.0.0</version>
    </dependency>
    <dependency>
        <groupId>ca.uhn.hapi.fhir</groupId>
        <artifactId>hapi-fhir-validation-resources-r4</artifactId>
        <version>4.0.0</version>
    </dependency>

Thanks in advance,
Johanna

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:13):

if you are using HAPI, then you can use ProfileUtilities to do this

view this post on Zulip Johanna Eicher (Sep 19 2019 at 09:29):

if you are using HAPI, then you can use ProfileUtilities to do this

Yes, that's what I did but I'm not sure how to invoke the method. This is what I tried:

class MyValidationSupport implements IValidationSupport {

    @Override
    public ValueSetExpansionOutcome expandValueSet(FhirContext theContext,
                                                   ConceptSetComponent theInclude) {
        return null;
    }

    @Override
    public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
        return null;
    }

    @Override
    public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
        return structureDefinitions;
    }

    @Override
    public CodeSystem fetchCodeSystem(FhirContext theContext, String theUri) {
        return codeSystemsMap.get(theUri);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends IBaseResource> T fetchResource(FhirContext theContext,
                                                     Class<T> theClass,
                                                     String theUri) {
        Validate.notBlank(theUri, "theUri must not be null or blank");
        if (theClass.equals(StructureDefinition.class)) { return (T) fetchStructureDefinition(theContext,
                                                                                              theUri); }
        if (theClass.equals(ValueSet.class)) { return (T) fetchValueSet(theContext, theUri); }
        return null;
    }

    @Override
    public StructureDefinition fetchStructureDefinition(FhirContext theContext, String theUri) {
        StructureDefinition sd = structureDefinitionsMap.get(theUri);
        if (sd == null) { return sd; }
        return generateSnapshot(sd, theUri, sd.getUrl(), sd.getName());
    }

    @Override
    public ValueSet fetchValueSet(FhirContext theContext, String theUri) {
        return valueSetsMap.get(theUri);
    }

    @Override
    public StructureDefinition generateSnapshot(StructureDefinition theInput,
                                                String theUri,
                                                String theWebUrl,
                                                String theProfileName) {
        IWorkerContext context = new HapiWorkerContext(FHIRContext.instance(), this);
        ArrayList<ValidationMessage> messages = new ArrayList<>();

        StructureDefinition base = new DefaultProfileValidationSupport().fetchStructureDefinition(FHIRContext.instance(),
                                                                                                  theInput.getBaseDefinition());
        if (base == null) { throw new PreconditionFailedException("Unknown base definition: " +
                                                                  theInput.getBaseDefinition()); }

        new ProfileUtilities(context, messages, null).generateSnapshot(base,
                                                                       theInput,
                                                                       theUri,
                                                                       theWebUrl,
                                                                       theProfileName);

        return theInput;
    }

    @Override
    public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
        return codeSystems.contains(theSystem);
    }

    @Override
    public LookupCodeResult
           lookupCode(FhirContext theContext, String theSystem, String theCode) {
        return null;
    }

    private CodeValidationResult
            testIfConceptIsInList(CodeSystem theCodeSystem,
                                  String theCode,
                                  List<ConceptDefinitionComponent> conceptList,
                                  boolean theCaseSensitive) {
        String code = theCode;
        if (theCaseSensitive == false) {
            code = code.toUpperCase();
        }

        return testIfConceptIsInListInner(theCodeSystem, conceptList, theCaseSensitive, code);
    }

    private CodeValidationResult
            testIfConceptIsInListInner(CodeSystem theCodeSystem,
                                       List<ConceptDefinitionComponent> conceptList,
                                       boolean theCaseSensitive,
                                       String code) {
        CodeValidationResult retVal = null;
        for (ConceptDefinitionComponent next : conceptList) {
            String nextCandidate = next.getCode();
            if (theCaseSensitive == false) {
                nextCandidate = nextCandidate.toUpperCase();
            }
            if (nextCandidate.equals(code)) {
                retVal = new CodeValidationResult(next);
                break;
            }

            // recurse
            retVal = testIfConceptIsInList(theCodeSystem,
                                           code,
                                           next.getConcept(),
                                           theCaseSensitive);
            if (retVal != null) {
                break;
            }
        }

        if (retVal != null) {
            retVal.setCodeSystemName(theCodeSystem.getName());
            retVal.setCodeSystemVersion(theCodeSystem.getVersion());
        }

        return retVal;
    }

    @Override
    public CodeValidationResult validateCode(FhirContext theContext,
                                             String theCodeSystem,
                                             String theCode,
                                             String theDisplay) {
        CodeSystem cs = fetchCodeSystem(theContext, theCodeSystem);
        if (cs != null) {
            boolean caseSensitive = true;
            if (cs.hasCaseSensitive()) {
                caseSensitive = cs.getCaseSensitive();
            }

            CodeValidationResult retVal = testIfConceptIsInList(cs,
                                                                theCode,
                                                                cs.getConcept(),
                                                                caseSensitive);

            if (retVal != null) { return retVal; }
        }

        return new CodeValidationResult(IssueSeverity.WARNING,
                                        "Unknown code: " + theCodeSystem + " / " + theCode);
    }
}

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:36):

that didn't work?

view this post on Zulip Johanna Eicher (Sep 19 2019 at 09:41):

When validating a resource of type Organization for which a custom profile is defined I get lots of error messages looking like this:

Error in snapshot generation: Differential for https://fhir.bbmri.de/StructureDefinition/Biobank with id: Organization.telecom:url.system has an element that is not marked with a snapshot match

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:43):

that means you have a problem with your differential, probably in the order of elements

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:44):

if you think that's not the case, you should submit the differential as a bug report

view this post on Zulip Johanna Eicher (Sep 19 2019 at 09:45):

The differential can be used successfully to validate resources with the HL7 validation jar (https://wiki.hl7.org/Using_the_FHIR_Validator). Probably then the differential shouldn't be the cause?

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:47):

without producing that message in the output? That's surprising. I can't think how that error could be caused by something are doing in executing the ProfileUtilities code wrongly

view this post on Zulip Grahame Grieve (Sep 19 2019 at 09:52):

you can get more insight by doing setDebug(true) on the profile utilities before calling generateSnapshot

view this post on Zulip Johanna Eicher (Sep 19 2019 at 09:52):

without producing that message in the output? That's surprising. I can't think how that error could be caused by something are doing in executing the ProfileUtilities code wrongly

I will double check!

view this post on Zulip Johanna Eicher (Sep 19 2019 at 10:42):

without producing that message in the output? That's surprising. I can't think how that error could be caused by something are doing in executing the ProfileUtilities code wrongly

I will double check!

I have verified that I am able to successfully validate the same resource with the latest version from https://fhir.github.io/latest-ig-publisher/org.hl7.fhir.validator.jar.

view this post on Zulip Johanna Eicher (Sep 19 2019 at 12:22):

PP @ null / null : base = 0 to 25, diff = 0 to 46 (slicing = false, redirector = [])
- Organization: base = 0 (Organization) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
PP @ Organization / null : base = 1 to 25, diff = 0 to 46 (slicing = false, redirector = [])
- Organization.id: base = 1 (Organization.id) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.meta: base = 2 (Organization.meta) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.implicitRules: base = 3 (Organization.implicitRules) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.language: base = 4 (Organization.language) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.text: base = 5 (Organization.text) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.contained: base = 6 (Organization.contained) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
- Organization.extension: base = 7 (Organization.extension) to 25 (Organization.endpoint), diff = 0 (Organization.extension) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.extension)
PP @ Organization / null : base = 7 to 7, diff = 1 to 1 (slicing = true, redirector = [])
- Organization.extension: base = 7 (Organization.extension) to 7 (Organization.extension), diff = 1 (Organization.extension:description) to 1 (Organization.extension:description) (slicingDone = true) (diffpath= Organization.extension)
PP @ null / null : base = 0 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension: base = 0 (Extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
PP @ Extension / null : base = 1 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension.id: base = 1 (Extension.id) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.extension: base = 2 (Extension.extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.url: base = 3 (Extension.url) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.value[x]: base = 4 (Extension.value[x]) to 4 (Extension.value[x]), diff = 1 (Extension.value[x]) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.value[x])
Differential:
Extension.url : [0..null] id = Extension.url fixed=uri
Extension.value[x] : string[1..null] id = Extension.value[x]
Snapshot:
Extension : [0..*] id = Extension constraints=2
Extension.id : string[0..1] id = Extension.id
Extension.extension : Extension[0..*] (slicing by url) id = Extension.extension
Extension.url : null[1..1] id = Extension.url fixed=uri
Extension.value[x] : string[1..1] id = Extension.value[x]
PP @ Organization / null : base = 7 to 7, diff = 2 to 2 (slicing = true, redirector = [])
- Organization.extension: base = 7 (Organization.extension) to 7 (Organization.extension), diff = 2 (Organization.extension:juridicalPerson) to 2 (Organization.extension:juridicalPerson) (slicingDone = true) (diffpath= Organization.extension)
PP @ null / null : base = 0 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension: base = 0 (Extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
PP @ Extension / null : base = 1 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension.id: base = 1 (Extension.id) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.extension: base = 2 (Extension.extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.url: base = 3 (Extension.url) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.value[x]: base = 4 (Extension.value[x]) to 4 (Extension.value[x]), diff = 1 (Extension.value[x]) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.value[x])
Differential:
Extension.url : [0..null] id = Extension.url fixed=uri
Extension.value[x] : string[1..null] id = Extension.value[x]
Snapshot:
Extension : [0..*] id = Extension constraints=2
Extension.id : string[0..1] id = Extension.id
Extension.extension : Extension[0..*] (slicing by url) id = Extension.extension
Extension.url : null[1..1] id = Extension.url fixed=uri
Extension.value[x] : string[1..1] id = Extension.value[x]
PP @ Organization / null : base = 7 to 7, diff = 3 to 3 (slicing = true, redirector = [])
- Organization.extension: base = 7 (Organization.extension) to 7 (Organization.extension), diff = 3 (Organization.extension:qualityStandard) to 3 (Organization.extension:qualityStandard) (slicingDone = true) (diffpath= Organization.extension)
PP @ null / null : base = 0 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension: base = 0 (Extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
PP @ Extension / null : base = 1 to 4, diff = 0 to 1 (slicing = false, redirector = [])
- Extension.id: base = 1 (Extension.id) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.extension: base = 2 (Extension.extension) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.url: base = 3 (Extension.url) to 4 (Extension.value[x]), diff = 0 (Extension.url) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.url)
- Extension.value[x]: base = 4 (Extension.value[x]) to 4 (Extension.value[x]), diff = 1 (Extension.value[x]) to 1 (Extension.value[x]) (slicingDone = false) (diffpath= Extension.value[x])
Differential:
Extension.url : [0..null] id = Extension.url fixed=uri
Extension.value[x] : CodeableConcept[0..null] id = Extension.value[x]
Snapshot:
Extension : [0..*] id = Extension constraints=2
Extension.id : string[0..1] id = Extension.id
Extension.extension : Extension[0..*] (slicing by url) id = Extension.extension
Extension.url : null[1..1] id = Extension.url fixed=uri
Extension.value[x] : CodeableConcept[0..1] id = Extension.value[x]
- Organization.modifierExtension: base = 8 (Organization.modifierExtension) to 25 (Organization.endpoint), diff = 4 (Organization.identifier) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.identifier)
- Organization.identifier: base = 9 (Organization.identifier) to 25 (Organization.endpoint), diff = 4 (Organization.identifier) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.identifier)
PP @ Organization / null : base = 9 to 9, diff = 5 to 7 (slicing = true, redirector = [])
- Organization.identifier: base = 9 (Organization.identifier) to 9 (Organization.identifier), diff = 5 (Organization.identifier:Bbmri-EricId) to 7 (Organization.identifier:Bbmri-EricId.value) (slicingDone = true) (diffpath= Organization.identifier)
- Organization.active: base = 10 (Organization.active) to 25 (Organization.endpoint), diff = 8 (Organization.name) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.name)
- Organization.type: base = 11 (Organization.type) to 25 (Organization.endpoint), diff = 8 (Organization.name) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.name)
- Organization.name: base = 12 (Organization.name) to 25 (Organization.endpoint), diff = 8 (Organization.name) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.name)
- Organization.alias: base = 13 (Organization.alias) to 25 (Organization.endpoint), diff = 9 (Organization.alias) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.alias)
- Organization.telecom: base = 14 (Organization.telecom) to 25 (Organization.endpoint), diff = 10 (Organization.telecom) to 46 (Organization.contact:contact.address.country) (slicingDone = false) (diffpath= Organization.telecom)
PP @ Organization / null : base = 14 to 14, diff = 11 to 13 (slicing = true, redirector = [])
- Organization.telecom: base = 14 (Organization.telecom) to 14 (Organization.telecom), diff = 11 (Organization.telecom:url) to 13 (Organization.telecom:url.value) (slicingDone = true) (diffpath= Organization.telecom)
- Organization.address: base = 15 (Organization.address) to 25 (Organization.endpoint), diff = 14 (Organization.address) to 46 (Organiza

view this post on Zulip Johanna Eicher (Sep 19 2019 at 12:23):

This is an excerpt from my log, I don't really know what to look for there.

view this post on Zulip Johanna Eicher (Sep 19 2019 at 12:23):

Eventually, I will end up with the following exception:

java.lang.UnsupportedOperationException
at org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator$WorkerContextWrapper.expandVS(FhirInstanceValidator.java:376)
at org.hl7.fhir.r5.elementmodel.Element.getAsICoding(Element.java:807)
at org.hl7.fhir.r5.model.Base.castToCoding(Base.java:518)
at org.hl7.fhir.r5.utils.FHIRPathEngine.opMemberOf(FHIRPathEngine.java:1888)
at org.hl7.fhir.r5.utils.FHIRPathEngine.operate(FHIRPathEngine.java:1410)
at org.hl7.fhir.r5.utils.FHIRPathEngine.execute(FHIRPathEngine.java:1175)
at org.hl7.fhir.r5.utils.FHIRPathEngine.execute(FHIRPathEngine.java:1156)
at org.hl7.fhir.r5.utils.FHIRPathEngine.execute(FHIRPathEngine.java:1174)
at org.hl7.fhir.r5.utils.FHIRPathEngine.evaluate(FHIRPathEngine.java:546)
at org.hl7.fhir.r5.utils.FHIRPathEngine.evaluateToBoolean(FHIRPathEngine.java:614)
at org.hl7.fhir.r5.validation.InstanceValidator.evaluateSlicingExpression(InstanceValidator.java:2803)
at org.hl7.fhir.r5.validation.InstanceValidator.sliceMatches(InstanceValidator.java:2795)
at org.hl7.fhir.r5.validation.InstanceValidator.matchSlice(InstanceValidator.java:4313)
at org.hl7.fhir.r5.validation.InstanceValidator.assignChildren(InstanceValidator.java:4227)
at org.hl7.fhir.r5.validation.InstanceValidator.validateElement(InstanceValidator.java:3942)
at org.hl7.fhir.r5.validation.InstanceValidator.start(InstanceValidator.java:2966)
at org.hl7.fhir.r5.validation.InstanceValidator.validateResource(InstanceValidator.java:4526)
at org.hl7.fhir.r5.validation.InstanceValidator.validate(InstanceValidator.java:825)
at org.hl7.fhir.r5.validation.InstanceValidator.validate(InstanceValidator.java:751)
at org.hl7.fhir.common.hapi.validation.ValidatorWrapper.validate(ValidatorWrapper.java:116)
at org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator.validate(FhirInstanceValidator.java:216)
at org.hl7.fhir.r4.hapi.validation.BaseValidatorBridge.doValidate(BaseValidatorBridge.java:20)
at org.hl7.fhir.r4.hapi.validation.BaseValidatorBridge.validateResource(BaseValidatorBridge.java:43)
at org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator.validateResource(FhirInstanceValidator.java:42)
at ca.uhn.fhir.validation.FhirValidator.validateWithResult(FhirValidator.java:219)
at ca.uhn.fhir.validation.FhirValidator.validateWithResult(FhirValidator.java:186)
at de.etl.util.FHIRValidation.validate(FHIRValidation.java:425)
at de.etl.test.TestValidation.testBiobank(TestValidation.java:118)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

view this post on Zulip Grahame Grieve (Sep 19 2019 at 14:27):

it looks like it succeeded this time, based on the location of the actual error, which is inside your terminology service binding?

view this post on Zulip James Agnew (Sep 19 2019 at 21:41):

Within the HAPI ecosystem, you can use the SnapshotGeneratingValidationSupport class for snapshot generation if you only have differentials. You'll also need an instance of DefaultProfileValidationSupport to supply the standard resources.

You may also want to use PrePopulatedValidationSupport to inject any custom profile SD's you have, and then ValidationSupportChain to chain them all together (this is what you will pass to the FhirInstanceValidator module).

view this post on Zulip Johanna Eicher (Sep 20 2019 at 07:50):

Within the HAPI ecosystem, you can use the SnapshotGeneratingValidationSupport class for snapshot generation if you only have differentials. You'll also need an instance of DefaultProfileValidationSupport to supply the standard resources.

You may also want to use PrePopulatedValidationSupport to inject any custom profile SD's you have, and then ValidationSupportChain to chain them all together (this is what you will pass to the FhirInstanceValidator module).

I have tried different variants of the components you described but I'm afraid I haven't found the correct combination yet. Could you tell me which methods I need to implement in my custom validator and which validators I need to use in which order if I have:
- A number of custom value sets
- A number of custom code systems
- A number of custom structure definitions as differentials.

I tried to provide the differentials via my own validation support and then have the snapshots generated by the SnapshotGeneratingValidationSupport like so:

    ValidationSupportChain support = new ValidationSupportChain(myValidationSupport,
                                                                new SnapshotGeneratingValidationSupport(FHIRContext.instance(),
                                                                                                        myValidationSupport),
                                                                new DefaultProfileValidationSupport());
    FhirInstanceValidator instanceValidator = new FhirInstanceValidator(support);
    validator.registerValidatorModule(instanceValidator);

I got error messages such as "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided" which made me think that my set up is not correct.

Through debugging I found that none of the generateSnapshot methods of any of the three provided validators is invoked in the first place which made me think that I have to call it myself in my custom validation support. I tried this in different ways but still not successfully.

All these things I tried before I posted my original question when I realized that most helpful would be a working example (which I also tried to find via various searches).

I would be really grateful if someone could provide such an example!

view this post on Zulip Johanna Eicher (Sep 20 2019 at 07:51):

it looks like it succeeded this time, based on the location of the actual error, which is inside your terminology service binding?

Thanks for the tip! I do think there could be another problem relating to this but I'm quite sure that my set up is not correct yet and I am trying to get to the root cause first. Hence my previous reply.

view this post on Zulip James Agnew (Sep 20 2019 at 16:25):

@Johanna Eicher What you posted looks close- I think you need to be passing in an instance of DefaultProfileValidationSupport to the constructor of SnapshotGeneratingValidationSupport instead of myValidationSupport though.

view this post on Zulip Johanna Eicher (Sep 23 2019 at 07:29):

Thank you for the tip. I have tried that as well but unfortunately with the same result (...StructureDefinition has no snapshot...).

In all of my attempts I have not yet managed to have the method ValidationSupportChain.generateSnapshotinvoked. From analyzing the Hapi code I'm using (see dependencies above) I could not find the trigger either.

Does this mean I'm missing a dependency or maybe that I have to invoke it myself?

view this post on Zulip James Agnew (Sep 23 2019 at 09:34):

You do need to invoke it yourself on the structuredefinition before you can use it if you onlt have a differential.. Then you'd want to put it in the PrePopulated one

view this post on Zulip Johanna Eicher (Sep 24 2019 at 14:41):

I have now changed my implementation such that I only use DefaultProfileValidationSupportand PrePopulatedValidationSupport populated with my custom value sets, code systems and snapshots which I have generated from my custom differentials before:

    // Validator instance
    validator = FHIRContext.instance().newValidator();

    // Default validator
    DefaultProfileValidationSupport defSupport = new DefaultProfileValidationSupport();

    // Validator used for generating snapshots (manually)
    SnapshotGeneratingValidationSupport snapSupport = new SnapshotGeneratingValidationSupport(FHIRContext.instance(),
                                                                                              defSupport);

    // Validator providing snapshots generated from custom differentials
    PrePopulatedValidationSupport ppSupport = new PrePopulatedValidationSupport();

    // For each value set
    for (ValueSet valueSet : valueSetsMap.values()) {
        ppSupport.addValueSet(valueSet);
    }

    // For each code system
    for (CodeSystem codeSystem : codeSystemsMap.values()) {
        ppSupport.addCodeSystem(codeSystem);
    }

    // For each profile
    for (StructureDefinition sd : structureDefinitionsMap.values()) {
        snapSupport.generateSnapshot(sd, sd.getBaseDefinition(), sd.getUrl(), sd.getName());
        ppSupport.addStructureDefinition(sd);
    }

    // Add custom validation
    ValidationSupportChain support = new ValidationSupportChain(ppSupport, defSupport);
    FhirInstanceValidator instanceValidator = new FhirInstanceValidator(support);
    validator.registerValidatorModule(instanceValidator);

Is this now the right approach? I am getting error messages such as

Organization.extension[2].value.ofType(CodeableConcept) Code https://fhir.bbmri.de/CodeSystem/QualityStandard/oecd-guidelines was not validated because the code system is not present

referencing code systems that I have supplied to the validator before.

view this post on Zulip James Agnew (Sep 24 2019 at 19:35):

Hmm, what you have look right to me.

Can you try putting breakpoints in DefaultProfileValidationSupport to see what is happening with the code validation?

view this post on Zulip Johanna Eicher (Sep 25 2019 at 12:08):

Through debugging, I found that the problem was caused by the fact that the method

@Override
public boolean isCodeSystemSupported(FhirContext theContext, String theSystem) {
    return false;
}

of the PrePopulatedValidationSupport class is always returning false. Does this mean this validator is not designed to handle custom code systems in general?

I think I have finally found a solution that is working for me. Instead of the PrePopulatedValidationSupport I am using my own validator (in addition to the default validator) which I populate with snapshots of my differentials (just like I was doing with the PrePopulatedValidationSupport). But now I can also implement the above mentioned method which so far seems to have solved my issues.

view this post on Zulip Pedro Lauro (Feb 28 2020 at 22:20):

I'm trying to replicate this last example @Johanna Eicher posted. I'm testing with a custom profile and a custom extension, both written as differentials. Is important to say that this custom profile uses the custom extension.

        FhirContext ctx = .....

        // Validator instance
        FhirValidator validator = ctx.newValidator();

        // Default validator
        DefaultProfileValidationSupport defSupport = new DefaultProfileValidationSupport();

        // Validator used for generating snapshots (manually)
        SnapshotGeneratingValidationSupport snapSupport =
                new SnapshotGeneratingValidationSupport(ctx, defSupport);

        // Validator providing snapshots generated from custom differentials
        PrePopulatedValidationSupport ppSupport = new PrePopulatedValidationSupport();

        IParser fhirParser = ctx.newXmlParser();

        // consider that there's a folder with all custom profiles
        List<String> sdFiles = fileUtil.listFolder(pathToProfilesFolder);
        for (String sdPath : sdFiles) {

            StructureDefinition sd = fhirParser.parseResource(
                    StructureDefinition.class,
                    fileUtil.readFile(sdPath));

            // the error prints after this next method call for the profile
            snapSupport.generateSnapshot(
                    sd,
                    sd.getBaseDefinition(),
                    sd.getUrl(),
                    sd.getName());

            ppSupport.addStructureDefinition(sd);
        }

        ValidationSupportChain support = new ValidationSupportChain(ppSupport, defSupport);

        // Add custom validation
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(support);
        validator.registerValidatorModule(instanceValidator);

When I run this code, I get the following error log after the generateSnapshot method, only for the custom profile:

Failed to find referenced profile: [CanonicalType[http://example.com/fhir/r4/StructureDefinition/CustomExtension]]

That is exactly the URL for my custom extension.
So I thought I'd had to first include the extension and then the profile, changing the order of the listed files on my profiles folder. But that didn't changed anything. I'm still getting that error, no matter the order those are imported.
No exception is thrown, but I still didn't try to validate any resources even with that log message.
Should I just ignore that message log? Or should I first import all extensions and then create a new SnapshotGeneratingValidationSupport using this new 'map' of supported structures and then import the structures that makes references to those?
Imagine my implementation context where I have a lot of custom profiles, one referencing the other (no cyclic ref though). Should I always rebuild the snapshot before importing the next structure?
There's also the possibility that my custom structures were written incorrectly. They're created using Firely's Forge, and they seem alright, no errors or warnings. The tool appears to understand the reference between them.

view this post on Zulip Sajith Jamal (Mar 01 2020 at 22:47):

Did you perhaps not disable the default rules in your validator?

// Create a validator and configure it
validator = con.newValidator();

// Typically if you are doing profile validation, you want to disable
 // the schema/schematron validation since the profile will specify
 // all the same rules (and more)
validator.setValidateAgainstStandardSchema(false);
validator.setValidateAgainstStandardSchematron(false);

When the above is true, even though your validation rules allow it, the default rules block it, failing your validation

view this post on Zulip Morten Ernebjerg (Mar 02 2020 at 08:11):

I am seeing the same problem as @Johanna Eicher with the loading of custom value sets/ code systems. Depending on the ordering of the default and the PrePopulated, validation supports, the same problem also occurs even with standard value sets as described in this thread: https://chat.fhir.org/#narrow/stream/179167-hapi/topic/Custom.20ValueSet.20missing.20from.20validation

view this post on Zulip Pedro Lauro (Mar 02 2020 at 17:00):

@Morten Ernebjerg , my example of validation is for R4 FHIR version, and yours are for DSTU3 right? The PrePopulated class have different versions for each FHIR version, and looking at the R4 version, the isCodeSystemSupported() method doesn't have that fixed statement to always returns false:
R4 PrePopulatedValidationSupport.java

view this post on Zulip Pedro Lauro (Mar 02 2020 at 17:11):

Sajith Jamal said:

Did you perhaps not disable the default rules in your validator?

// Create a validator and configure it
validator = con.newValidator();

// Typically if you are doing profile validation, you want to disable
 // the schema/schematron validation since the profile will specify
 // all the same rules (and more)
validator.setValidateAgainstStandardSchema(false);
validator.setValidateAgainstStandardSchematron(false);

When the above is true, even though your validation rules allow it, the default rules block it, failing your validation

Even though those are false by default, I tried setting them false, with no difference on the output. Still getting the same error.

view this post on Zulip Sajith Jamal (Mar 03 2020 at 00:32):

hmmmm......
another thing to try is instead of this:

// Add custom validation
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(support);
        validator.registerValidatorModule(instanceValidator);

try this:

ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(), validationSupport);
instanceValidator.setValidationSupport(support);

its how I got mine to work...and seems to be the difference between your code and mine

view this post on Zulip Morten Ernebjerg (Mar 03 2020 at 07:17):

@Pedro Lauro Yes, I am using STU3 - thanks for pointing out the code difference to R4, didn't check that so far!

view this post on Zulip Pedro Lauro (Mar 04 2020 at 20:38):

Sajith Jamal said:

hmmmm......
another thing to try is instead of this:

// Add custom validation
        FhirInstanceValidator instanceValidator = new FhirInstanceValidator(support);
        validator.registerValidatorModule(instanceValidator);

try this:

ValidationSupportChain support = new ValidationSupportChain(new DefaultProfileValidationSupport(), validationSupport);
instanceValidator.setValidationSupport(support);

its how I got mine to work...and seems to be the difference between your code and mine

Nothing different. The error happens before those instructions, while trying to generate the snapshot.


Last updated: Apr 12 2022 at 19:14 UTC