Stream: hapi
Topic: Example: Custom profile validation
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
Grahame Grieve (Sep 19 2019 at 09:13):
if you are using HAPI, then you can use ProfileUtilities to do this
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); } }
Grahame Grieve (Sep 19 2019 at 09:36):
that didn't work?
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
Grahame Grieve (Sep 19 2019 at 09:43):
that means you have a problem with your differential, probably in the order of elements
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
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?
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
Grahame Grieve (Sep 19 2019 at 09:52):
you can get more insight by doing setDebug(true) on the profile utilities before calling generateSnapshot
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!
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.
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
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.
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)
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?
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).
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!
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.
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.
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.generateSnapshot
invoked. 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?
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
Johanna Eicher (Sep 24 2019 at 14:41):
I have now changed my implementation such that I only use DefaultProfileValidationSupport
and 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.
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?
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.
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.
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
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
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
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.
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
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!
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