Extension domains configuration for the fhr instance validator (#917)

* Fix the url of the StructureDefinition extensions

http://hl7.org/fhir/tools/StructureDefinition/ -> http://hl7.org/fhir/StructureDefinition/
That makes the entry for the structuredefinition-expression superfluous.

The url behind IG_DEPENDSON_PACKAGE_EXTENSION does not point is not (yet?) valid too.

* Add ability to configure custom extension domains.

* Testing the extension domains configuration

* Fix obvious bug when determining resource name

The for loop was completely superfluous beforehand.
Either fix it as e.g. in this patch or remove it.

* small improvements and TODO's for possible NPE's

- javadoc see
- use diamond operator for generics
- remove throws clause when exception is not thrown
- add TODO's for some cases a NPE might occur.
  My knowledge of the context is not sufficient to suggest the proper
  way to solve this.
This commit is contained in:
hdconradi 2018-12-13 11:32:47 +01:00 committed by James Agnew
parent 1afe36e60a
commit 89ede0e524
5 changed files with 395 additions and 29 deletions

View File

@ -60,6 +60,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false; private boolean noTerminologyChecks = false;
private volatile WorkerContextWrapper myWrappedWorkerContext; private volatile WorkerContextWrapper myWrappedWorkerContext;
private List<String> extensionDomains = Collections.emptyList();
/** /**
* Constructor * Constructor
@ -81,18 +82,52 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
private String determineResourceName(Document theDocument) { /**
Element root = null; * Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
return this;
}
/**
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes(); NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) { for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) { if (list.item(i) instanceof Element) {
root = (Element) list.item(i); return list.item(i).getLocalName();
break;
} }
} }
root = theDocument.getDocumentElement(); return theDocument.getDocumentElement().getLocalName();
return root.getLocalName();
} }
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) { private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
@ -138,7 +173,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* guielines will be ignored. * guielines will be ignored.
* </p> * </p>
* *
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)} * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
*/ */
public BestPracticeWarningLevel getBestPracticeWarningLevel() { public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel; return myBestPracticeWarningLevel;
@ -235,6 +270,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed()); v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL); v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks()); v.setNoTerminologyChecks(isNoTerminologyChecks());
v.addExtensionDomains(extensionDomains);
List<ValidationMessage> messages = new ArrayList<>(); List<ValidationMessage> messages = new ArrayList<>();
@ -343,7 +379,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private LoadingCache<ResourceKey, org.hl7.fhir.r4.model.Resource> myFetchResourceCache; private LoadingCache<ResourceKey, org.hl7.fhir.r4.model.Resource> myFetchResourceCache;
private org.hl7.fhir.r4.model.Parameters myExpansionProfile; private org.hl7.fhir.r4.model.Parameters myExpansionProfile;
public WorkerContextWrapper(HapiWorkerContext theWorkerContext) { WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext; myWrap = theWorkerContext;
myConverter = new VersionConvertor_30_40(); myConverter = new VersionConvertor_30_40();
@ -424,7 +460,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public void cacheResource(org.hl7.fhir.r4.model.Resource res) throws FHIRException { public void cacheResource(org.hl7.fhir.r4.model.Resource res) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -446,7 +482,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override @Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) { public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
ValueSet convertedSource = null; ValueSet convertedSource;
try { try {
convertedSource = VersionConvertor_30_40.convertValueSet(source); convertedSource = VersionConvertor_30_40.convertValueSet(source);
} catch (FHIRException e) { } catch (FHIRException e) {
@ -470,7 +506,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException { public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -637,7 +673,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public IResourceValidator newValidator() throws FHIRException { public IResourceValidator newValidator() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@ -662,7 +698,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public boolean supportsSystem(String system) throws TerminologyServiceException { public boolean supportsSystem(String system) {
return myWrap.supportsSystem(system); return myWrap.supportsSystem(system);
} }
@ -679,6 +715,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override @Override
public ValidationResult validateCode(String system, String code, String display) { public ValidationResult validateCode(String system, String code, String display) {
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
// TODO: converted code might be null -> NPE
return convertValidationResult(result); return convertValidationResult(result);
} }
@ -729,6 +766,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result); return convertValidationResult(result);
} }
@ -749,6 +787,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e); throw new InternalErrorException(e);
} }
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs); org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result); return convertValidationResult(result);
} }

View File

@ -32,6 +32,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader; import java.io.StringReader;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -45,6 +46,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private DocumentBuilderFactory myDocBuilderFactory; private DocumentBuilderFactory myDocBuilderFactory;
private boolean myNoTerminologyChecks; private boolean myNoTerminologyChecks;
private StructureDefinition myStructureDefintion; private StructureDefinition myStructureDefintion;
private List<String> extensionDomains = Collections.emptyList();
private IValidationSupport myValidationSupport; private IValidationSupport myValidationSupport;
@ -68,18 +70,52 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myValidationSupport = theValidationSupport; myValidationSupport = theValidationSupport;
} }
private String determineResourceName(Document theDocument) { /**
Element root = null; * Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
return this;
}
/**
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
* considered as known.
* <p>
* Any unknown extension domain will result in an information message when validating a resource.
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes(); NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) { for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) { if (list.item(i) instanceof Element) {
root = (Element) list.item(i); return list.item(i).getLocalName();
break;
} }
} }
root = theDocument.getDocumentElement(); return theDocument.getDocumentElement().getLocalName();
return root.getLocalName();
} }
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) { private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
@ -120,8 +156,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice * reported at the ERROR level. If this setting is set to {@link BestPracticeWarningLevel#Ignore}, best practice
* guielines will be ignored. * guielines will be ignored.
* </p> * </p>
* *
* @see {@link #setBestPracticeWarningLevel(BestPracticeWarningLevel)} * @see #setBestPracticeWarningLevel(BestPracticeWarningLevel)
*/ */
public BestPracticeWarningLevel getBestPracticeWarningLevel() { public BestPracticeWarningLevel getBestPracticeWarningLevel() {
return myBestPracticeWarningLevel; return myBestPracticeWarningLevel;
@ -211,8 +247,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed()); v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL); v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks()); v.setNoTerminologyChecks(isNoTerminologyChecks());
v.addExtensionDomains(extensionDomains);
List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) { if (theEncoding == EncodingEnum.XML) {
Document document; Document document;
@ -312,7 +349,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
public static class NullEvaluationContext implements IEvaluationContext { public static class NullEvaluationContext implements IEvaluationContext {
@Override @Override
public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) throws PathEngineException { public TypeDetails checkFunction(Object theAppContext, String theFunctionName, List<TypeDetails> theParameters) {
return null; return null;
} }
@ -327,12 +364,12 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
} }
@Override @Override
public Base resolveConstant(Object theAppContext, String theName) throws PathEngineException { public Base resolveConstant(Object theAppContext, String theName) {
return null; return null;
} }
@Override @Override
public TypeDetails resolveConstantType(Object theAppContext, String theName) throws PathEngineException { public TypeDetails resolveConstantType(Object theAppContext, String theName) {
return null; return null;
} }

View File

@ -416,8 +416,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private boolean allowUnknownExtension(String url) { private boolean allowUnknownExtension(String url) {
if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression")) if (isPredefinedExtension(url))
// Added structuredefinition-expression explicitly because it wasn't defined in the version of the spec it needs to be used with
return true; return true;
for (String s : extensionDomains) for (String s : extensionDomains)
if (url.startsWith(s)) if (url.startsWith(s))
@ -426,8 +425,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
private boolean isKnownExtension(String url) { private boolean isKnownExtension(String url) {
// Added structuredefinition-expression and following extensions explicitly because they weren't defined in the version of the spec they need to be used with if (isPredefinedExtension(url))
if (url.contains("example.org") || url.contains("acme.com") || url.contains("nema.org") || url.startsWith("http://hl7.org/fhir/tools/StructureDefinition/") || url.equals("http://hl7.org/fhir/StructureDefinition/structuredefinition-expression") || url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION))
return true; return true;
for (String s : extensionDomains) for (String s : extensionDomains)
if (url.startsWith(s)) if (url.startsWith(s))
@ -435,6 +433,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false; return false;
} }
private boolean isPredefinedExtension(String url) {
return url.contains("example.org")
|| url.contains("acme.com")
|| url.contains("nema.org")
|| url.startsWith("http://hl7.org/fhir/StructureDefinition/")
|| url.equals(VersionConvertorConstants.IG_DEPENDSON_PACKAGE_EXTENSION);
}
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) { private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
if (bpWarnings != null) { if (bpWarnings != null) {
switch (bpWarnings) { switch (bpWarnings) {
@ -1950,6 +1956,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return extensionDomains; return extensionDomains;
} }
public InstanceValidator addExtensionDomains(List<String> extensionDomains) {
this.extensionDomains.addAll(extensionDomains);
return this;
}
private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) { private Element getFromBundle(Element bundle, String ref, String fullUrl, List<ValidationMessage> errors, String path) {
String targetUrl = null; String targetUrl = null;
String version = ""; String version = "";

View File

@ -0,0 +1,134 @@
package org.hl7.fhir.dstu3.hapi.validation;
import java.util.Collections;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.ValidationResult;
import org.hamcrest.Matchers;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Enumerations.PublicationStatus;
import org.hl7.fhir.dstu3.model.Questionnaire;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class QuestionnaireValidatorDstu3Test {
private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorDstu3Test.class);
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forDstu3();
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
@Before
public void before() {
IValidationSupport myValSupport = mock(IValidationSupport.class);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCoding() {
String[] extensionDomainsToTest = new String[] {
"http://example.org/questionnaire-color-control-1",
"https://example.org/questionnaire-color-control-2",
"http://acme.com/questionnaire-color-control-3",
"https://acme.com/questionnaire-color-control-4",
"http://nema.org/questionnaire-color-control-5",
"https://nema.org/questionnaire-color-control-6",
"http://hl7.org/fhir/StructureDefinition/questionnaire-scoreItem",
"http://hl7.org/fhir/StructureDefinition/structuredefinition-expression",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = new Questionnaire();
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCodeableConcept() {
String[] extensionDomainsToTest = new String[] {
"http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = new Questionnaire();
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new CodeableConcept().addCoding(new Coding(null, "text-box", null)));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithCustomExtensionDomain() {
Questionnaire q = new Questionnaire();
String extensionUrl = "http://my.own.domain/StructureDefinition/";
q.setStatus(PublicationStatus.ACTIVE)
.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionUrl + "questionnaire-itemControl")
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.hasSize(1));
assertEquals(errors.getMessages().get(0).getSeverity(), ResultSeverityEnum.INFORMATION);
assertThat(errors.getMessages().get(0).getMessage(), Matchers.startsWith("Unknown extension " + extensionUrl));
myInstanceVal.setCustomExtensionDomains(Collections.singletonList(extensionUrl));
errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -0,0 +1,145 @@
package org.hl7.fhir.r4.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.ValidationResult;
import org.hamcrest.Matchers;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.Coding;
import org.hl7.fhir.r4.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r4.model.Questionnaire;
import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemType;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
public class QuestionnaireValidatorR4Test {
private static final Logger ourLog = LoggerFactory.getLogger(QuestionnaireValidatorR4Test.class);
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forR4();
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
@Before
public void before() {
IValidationSupport myValSupport = mock(IValidationSupport.class);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomains() {
String[] extensionDomainsToTest = new String[] {
"http://example.org/questionnaire-color-control-1",
"https://example.org/questionnaire-color-control-2",
"http://acme.com/questionnaire-color-control-3",
"https://acme.com/questionnaire-color-control-4",
"http://nema.org/questionnaire-color-control-5",
"https://nema.org/questionnaire-color-control-6",
"http://hl7.org/fhir/StructureDefinition/questionnaire-scoreItem",
"http://hl7.org/fhir/StructureDefinition/structuredefinition-expression",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithPredefinedExtensionDomainsForCodeableConcept() {
String[] extensionDomainsToTest = new String[] {
"http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
};
for (String extensionDomainToTest : extensionDomainsToTest) {
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionDomainToTest)
.setValue(new CodeableConcept().addCoding(new Coding(null, "text-box", null)));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
}
@Test
public void testQuestionnaireWithCustomExtensionDomain() {
String extensionUrl = "http://my.own.domain/StructureDefinition/";
Questionnaire q = minimalValidQuestionnaire();
q.addItem()
.setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.addExtension()
.setUrl(extensionUrl + "questionnaire-itemControl")
.setValue(new Coding(null, "text-box", null));
ValidationResult errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.hasSize(1));
assertEquals(errors.getMessages().get(0).getSeverity(), ResultSeverityEnum.INFORMATION);
assertThat(errors.getMessages().get(0).getMessage(), Matchers.startsWith("Unknown extension " + extensionUrl));
myInstanceVal.setCustomExtensionDomains(extensionUrl);
errors = myVal.validateWithResult(q);
ourLog.info(errors.toString());
assertThat(errors.isSuccessful(), Matchers.is(true));
assertThat(errors.getMessages(), Matchers.empty());
}
private Questionnaire minimalValidQuestionnaire() {
Narrative n = new Narrative().setStatus(NarrativeStatus.GENERATED);
n.setDivAsString("simple example");
Questionnaire q = new Questionnaire();
q.setText(n);
q.setName("SomeName");
q.setStatus(PublicationStatus.ACTIVE);
return q;
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}