diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml
index c2795fa0544..c9aa6d46be1 100644
--- a/hapi-fhir-converter/pom.xml
+++ b/hapi-fhir-converter/pom.xml
@@ -16,14 +16,14 @@
ca.uhn.hapi.fhir
hapi-fhir-base
- 3.7.0-SNAPSHOT
+ ${project.version}
ca.uhn.hapi.fhir
hapi-fhir-server
- 3.7.0-SNAPSHOT
+ ${project.version}
true
@@ -35,43 +35,43 @@
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-structures-hl7org-dstu2
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu2.1
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-structures-dstu3
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-structures-r4
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-validation-resources-dstu2
- 3.7.0-SNAPSHOT
+ ${project.version}
true
ca.uhn.hapi.fhir
hapi-fhir-validation-resources-dstu3
- 3.7.0-SNAPSHOT
+ ${project.version}
true
diff --git a/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java
index e97fb121e3c..579acd9a82a 100644
--- a/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java
+++ b/hapi-fhir-converter/src/main/java/org/hl7/fhir/convertors/VersionConvertor_30_40.java
@@ -16013,6 +16013,7 @@ public class VersionConvertor_30_40 {
tgt.setType(convertQuestionnaireItemType(src.getType()));
for (org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent t : src.getEnableWhen())
tgt.addEnableWhen(convertQuestionnaireItemEnableWhenComponent(t));
+ tgt.setEnableBehavior(EnableWhenBehavior.ANY);
if (src.hasRequired())
tgt.setRequired(src.getRequired());
if (src.hasRepeats())
@@ -16029,6 +16030,9 @@ public class VersionConvertor_30_40 {
tgt.addInitial().setValue(convertType(src.getInitial()));
for (org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent t : src.getItem())
tgt.addItem(convertQuestionnaireItemComponent(t));
+ for (org.hl7.fhir.dstu3.model.Extension t : src.getModifierExtension()) {
+ tgt.addModifierExtension(convertExtension(t));
+ }
return tgt;
}
@@ -16131,8 +16135,10 @@ public class VersionConvertor_30_40 {
tgt.setOperator(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemOperator.EXISTS);
tgt.setAnswer(convertType(src.getHasAnswerElement()));
}
- else if (src.hasAnswer())
- tgt.setAnswer(convertType(src.getAnswer()));
+ else if (src.hasAnswer()) {
+ tgt.setOperator(org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemOperator.EQUAL);
+ tgt.setAnswer(convertType(src.getAnswer()));
+ }
return tgt;
}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java
index 146b9d737fc..725d085dc49 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.java
@@ -31,6 +31,8 @@ import org.hl7.fhir.r4.utils.INarrativeGenerator;
import org.hl7.fhir.r4.utils.IResourceValidator;
import org.hl7.fhir.r4.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r4.utils.IResourceValidator.IdStatus;
+import org.hl7.fhir.r4.validation.DefaultEnableWhenEvaluator;
+import org.hl7.fhir.r4.validation.IEnableWhenEvaluator;
import org.hl7.fhir.r4.validation.InstanceValidator;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@@ -47,6 +49,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
@SuppressWarnings({"PackageAccessibility", "Duplicates"})
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidatorModule {
@@ -60,6 +63,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
private volatile WorkerContextWrapper myWrappedWorkerContext;
+ private Function enableWhenEvaluatorSupplier = ctx -> new DefaultEnableWhenEvaluator();
+
+ private boolean errorForUnknownProfiles;
private List extensionDomains = Collections.emptyList();
/**
@@ -221,6 +227,14 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
+
+ public boolean isErrorForUnknownProfiles() {
+ return errorForUnknownProfiles;
+ }
+
+ public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
+ this.errorForUnknownProfiles = errorForUnknownProfiles;
+ }
/**
* If set to {@literal true} (default is true) extensions which are not known to the
@@ -237,6 +251,15 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
+
+ /**
+ * Sets a customized {@link IEnableWhenEvaluator} which is injected to created InstanceValidators
+ * @param myEnableWhenEvaluator
+ */
+ public void setEnableWhenEvaluatorSupplier(
+ Function enableWhenEvaluatorSupplier) {
+ this.enableWhenEvaluatorSupplier = enableWhenEvaluatorSupplier;
+ }
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
@@ -270,7 +293,9 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
- v.getExtensionDomains().addAll(extensionDomains);
+ v.setMyEnableWhenEvaluator(enableWhenEvaluatorSupplier.apply(wrappedWorkerContext));
+ v.setErrorForUnknownProfiles(isErrorForUnknownProfiles());
+ v.addExtensionDomains(extensionDomains);
List messages = new ArrayList<>();
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java
index 36df4e23dfe..8fc9f6a0489 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/InstanceValidator.java
@@ -2539,6 +2539,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress);
else
rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
+
}
}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/QuestionnaireResponseValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/QuestionnaireResponseValidator.java
index ef430b4d3ef..1209c78c8a4 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/QuestionnaireResponseValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/validation/QuestionnaireResponseValidator.java
@@ -390,3 +390,4 @@ public class QuestionnaireResponseValidator extends BaseValidator {
return allowedAnswerTypes;
}
}
+
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/EnableWhenResult.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/EnableWhenResult.java
new file mode 100644
index 00000000000..84afebe2980
--- /dev/null
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/instance/validation/EnableWhenResult.java
@@ -0,0 +1,48 @@
+package org.hl7.fhir.instance.validation;
+
+
+import org.hl7.fhir.r4.elementmodel.Element;
+import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
+
+public class EnableWhenResult {
+ private final boolean enabled;
+ private final QuestionnaireItemEnableWhenComponent enableWhenCondition;
+ private final Element answerItem;
+ private final String linkId;
+
+ /**
+ * Evaluation result of enableWhen condition
+ *
+ * @param enabled
+ * Evaluation result
+ * @param linkId
+ * LinkId of the questionnaire item
+ * @param enableWhenCondition
+ * Evaluated enableWhen condition
+ * @param answerItem
+ * item in QuestionnaireResponse
+ */
+ public EnableWhenResult(boolean enabled, String linkId, QuestionnaireItemEnableWhenComponent enableWhenCondition,
+ Element answerItem) {
+ this.enabled = enabled;
+ this.linkId = linkId;
+ this.answerItem = answerItem;
+ this.enableWhenCondition = enableWhenCondition;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public String getLinkId() {
+ return linkId;
+ }
+
+ public Element getAnswerItem() {
+ return answerItem;
+ }
+
+ public QuestionnaireItemEnableWhenComponent getEnableWhenCondition() {
+ return enableWhenCondition;
+ }
+}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java
new file mode 100644
index 00000000000..d070570c816
--- /dev/null
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/DefaultEnableWhenEvaluator.java
@@ -0,0 +1,216 @@
+package org.hl7.fhir.r4.validation;
+
+import java.util.*;
+import java.util.stream.*;
+
+import org.hl7.fhir.exceptions.FHIRException;
+import org.hl7.fhir.r4.elementmodel.Element;
+import org.hl7.fhir.r4.model.*;
+import org.hl7.fhir.r4.model.Questionnaire.*;
+
+import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
+
+/**
+ * Evaluates Questionnaire.item.enableWhen against a QuestionnaireResponse.
+ * Ignores possible modifierExtensions and extensions.
+ *
+ */
+public class DefaultEnableWhenEvaluator implements IEnableWhenEvaluator {
+ public static final String LINKID_ELEMENT = "linkId";
+ public static final String ITEM_ELEMENT = "item";
+ public static final String ANSWER_ELEMENT = "answer";
+
+ @Override
+ public boolean isQuestionEnabled(QuestionnaireItemComponent questionnaireItem, Element questionnaireResponse) {
+ if (!questionnaireItem.hasEnableWhen()) {
+ return true;
+ }
+ List evaluationResults = questionnaireItem.getEnableWhen()
+ .stream()
+ .map(enableCondition -> evaluateCondition(enableCondition, questionnaireResponse,
+ questionnaireItem.getLinkId()))
+ .collect(Collectors.toList());
+ return checkConditionResults(evaluationResults, questionnaireItem);
+ }
+
+
+ public boolean checkConditionResults(List evaluationResults,
+ QuestionnaireItemComponent questionnaireItem) {
+ if (questionnaireItem.hasEnableBehavior() && questionnaireItem.getEnableBehavior() == EnableWhenBehavior.ANY){
+ return evaluationResults.stream().anyMatch(EnableWhenResult::isEnabled);
+ } if (questionnaireItem.hasEnableBehavior() && questionnaireItem.getEnableBehavior() == EnableWhenBehavior.ALL){
+ return evaluationResults.stream().allMatch(EnableWhenResult::isEnabled);
+ }
+ //TODO: Throw exception? enableBehavior is mandatory when there are multiple conditions
+ return true;
+ }
+
+
+ protected EnableWhenResult evaluateCondition(QuestionnaireItemEnableWhenComponent enableCondition,
+ Element questionnaireResponse, String linkId) {
+ //TODO: Fix EnableWhenResult stuff
+ List answerItems = findQuestionAnswers(questionnaireResponse,
+ enableCondition.getQuestion());
+ QuestionnaireItemOperator operator = enableCondition.getOperator();
+ if (operator == QuestionnaireItemOperator.EXISTS){
+ Type answer = enableCondition.getAnswer();
+ if (!(answer instanceof BooleanType)){
+ throw new UnprocessableEntityException("Exists-operator requires answerBoolean");
+ }
+ return new EnableWhenResult(((BooleanType)answer).booleanValue() != answerItems.isEmpty(),
+ linkId, enableCondition, questionnaireResponse);
+ }
+ boolean result = answerItems
+ .stream()
+ .anyMatch(answer -> evaluateAnswer(answer, enableCondition.getAnswer(), enableCondition.getOperator()));
+ return new EnableWhenResult(result, linkId, enableCondition, questionnaireResponse);
+ }
+
+ public Type convertToType(Element element) {
+ Type b = new Factory().create(element.fhirType());
+ if (b instanceof PrimitiveType) {
+ ((PrimitiveType>) b).setValueAsString(element.primitiveValue());
+ } else {
+ for (Element child : element.getChildren()) {
+ if (!isExtension(child)) {
+ b.setProperty(child.getName(), convertToType(child));
+ }
+ }
+ }
+ return b;
+ }
+
+
+ private boolean isExtension(Element element) {
+ return "Extension".equals(element.fhirType());
+ }
+
+ protected boolean evaluateAnswer(Element answer, Type expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
+ Type actualAnswer;
+ if (isExtension(answer)) {
+ return false;
+ }
+ try {
+ actualAnswer = convertToType(answer);
+ } catch (FHIRException e) {
+ throw new UnprocessableEntityException("Unexpected answer type", e);
+ }
+ if (!actualAnswer.getClass().equals(expectedAnswer.getClass())) {
+ throw new UnprocessableEntityException("Expected answer and actual answer have incompatible types");
+ }
+ if (expectedAnswer instanceof Coding) {
+ return compareCodingAnswer((Coding)expectedAnswer, (Coding)actualAnswer, questionnaireItemOperator);
+ } else if ((expectedAnswer instanceof PrimitiveType)) {
+ return comparePrimitiveAnswer((PrimitiveType>)actualAnswer, (PrimitiveType>)expectedAnswer, questionnaireItemOperator);
+ } else if (expectedAnswer instanceof Quantity) {
+ return compareQuantityAnswer((Quantity)actualAnswer, (Quantity)expectedAnswer, questionnaireItemOperator);
+ }
+ // TODO: Attachment, reference?
+ throw new UnprocessableEntityException("Unimplemented answer type: " + expectedAnswer.getClass());
+ }
+
+
+ private boolean compareQuantityAnswer(Quantity actualAnswer, Quantity expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
+ return compareComparable(actualAnswer.getValue(), expectedAnswer.getValue(), questionnaireItemOperator);
+ }
+
+
+ private boolean comparePrimitiveAnswer(PrimitiveType> actualAnswer, PrimitiveType> expectedAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
+ if (actualAnswer.getValue() instanceof Comparable){
+ return compareComparable((Comparable)actualAnswer.getValue(), (Comparable) expectedAnswer.getValue(), questionnaireItemOperator);
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
+ return actualAnswer.equalsShallow(expectedAnswer);
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
+ return !actualAnswer.equalsShallow(expectedAnswer);
+ }
+ throw new UnprocessableEntityException("Bad operator for PrimitiveType comparison");
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private boolean compareComparable(Comparable actual, Comparable expected,
+ QuestionnaireItemOperator questionnaireItemOperator) {
+ int result = actual.compareTo(expected);
+
+ if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
+ return result == 0;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
+ return result != 0;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_OR_EQUAL){
+ return result >= 0;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_OR_EQUAL){
+ return result <= 0;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.LESS_THAN){
+ return result < 0;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.GREATER_THAN){
+ return result > 0;
+ }
+
+ throw new UnprocessableEntityException("Bad operator for PrimitiveType comparison");
+
+ }
+
+ private List findQuestionAnswers(Element questionnaireResponse, String question) {
+ List matchingItems = questionnaireResponse.getChildren(ITEM_ELEMENT)
+ .stream()
+ .flatMap(i -> findSubItems(i).stream())
+ .filter(i -> hasLinkId(i, question))
+ .collect(Collectors.toList());
+ return matchingItems
+ .stream()
+ .flatMap(e -> extractAnswer(e).stream())
+ .collect(Collectors.toList());
+ }
+
+ private List extractAnswer(Element item) {
+ return item.getChildrenByName(ANSWER_ELEMENT)
+ .stream()
+ .flatMap(c -> c.getChildren().stream())
+ .collect(Collectors.toList());
+ }
+
+ private boolean compareCodingAnswer(Coding expectedAnswer, Coding actualAnswer, QuestionnaireItemOperator questionnaireItemOperator) {
+ boolean result = compareSystems(expectedAnswer, actualAnswer) && compareCodes(expectedAnswer, actualAnswer);
+ if (questionnaireItemOperator == QuestionnaireItemOperator.EQUAL){
+ return result == true;
+ } else if (questionnaireItemOperator == QuestionnaireItemOperator.NOT_EQUAL){
+ return result == false;
+ }
+ throw new UnprocessableEntityException("Bad operator for Coding comparison");
+ }
+
+ private boolean compareCodes(Coding expectedCoding, Coding value) {
+ if (expectedCoding.hasCode() != value.hasCode()) {
+ return false;
+ }
+ if (expectedCoding.hasCode()) {
+ return expectedCoding.getCode().equals(value.getCode());
+ }
+ return true;
+ }
+
+ private boolean compareSystems(Coding expectedCoding, Coding value) {
+ if (expectedCoding.hasSystem() && !value.hasSystem()) {
+ return false;
+ }
+ if (expectedCoding.hasSystem()) {
+ return expectedCoding.getSystem().equals(value.getSystem());
+ }
+ return true;
+ }
+ private List findSubItems(Element item) {
+ List results = item.getChildren(LINKID_ELEMENT)
+ .stream()
+ .flatMap(i -> findSubItems(i).stream())
+ .collect(Collectors.toList());
+ results.add(item);
+ return results;
+ }
+
+ private boolean hasLinkId(Element item, String linkId) {
+ Element linkIdChild = item.getNamedChild(LINKID_ELEMENT);
+ if (linkIdChild != null && linkIdChild.getValue().equals(linkId)){
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/EnableWhenResult.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/EnableWhenResult.java
new file mode 100644
index 00000000000..4f6d5d7f7db
--- /dev/null
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/EnableWhenResult.java
@@ -0,0 +1,48 @@
+package org.hl7.fhir.r4.validation;
+
+
+import org.hl7.fhir.r4.elementmodel.Element;
+import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
+
+public class EnableWhenResult {
+ private final boolean enabled;
+ private final QuestionnaireItemEnableWhenComponent enableWhenCondition;
+ private final Element answerItem;
+ private final String linkId;
+
+ /**
+ * Evaluation result of enableWhen condition
+ *
+ * @param enabled
+ * Evaluation result
+ * @param linkId
+ * LinkId of the questionnaire item
+ * @param enableWhenCondition
+ * Evaluated enableWhen condition
+ * @param responseItem
+ * item in QuestionnaireResponse
+ */
+ public EnableWhenResult(boolean enabled, String linkId, QuestionnaireItemEnableWhenComponent enableWhenCondition,
+ Element answerItem) {
+ this.enabled = enabled;
+ this.linkId = linkId;
+ this.answerItem = answerItem;
+ this.enableWhenCondition = enableWhenCondition;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public String getLinkId() {
+ return linkId;
+ }
+
+ public Element getAnswerItem() {
+ return answerItem;
+ }
+
+ public QuestionnaireItemEnableWhenComponent getEnableWhenCondition() {
+ return enableWhenCondition;
+ }
+}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/IEnableWhenEvaluator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/IEnableWhenEvaluator.java
new file mode 100644
index 00000000000..be567c9d8d4
--- /dev/null
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/IEnableWhenEvaluator.java
@@ -0,0 +1,10 @@
+package org.hl7.fhir.r4.validation;
+
+import org.hl7.fhir.r4.elementmodel.Element;
+import org.hl7.fhir.r4.model.Questionnaire.QuestionnaireItemComponent;
+
+public interface IEnableWhenEvaluator {
+ public boolean isQuestionEnabled(QuestionnaireItemComponent questionnaireItem,
+ Element questionnaireResponse);
+
+}
diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java
index 9654b1c0d95..1821523b812 100644
--- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java
+++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/validation/InstanceValidator.java
@@ -15,6 +15,8 @@ import java.util.UUID;
import org.apache.commons.lang3.NotImplementedException;
import org.apache.commons.lang3.StringUtils;
+import org.hl7.fhir.r4.model.Reference;
+import org.hl7.fhir.exceptions.*;
import org.hl7.fhir.convertors.VersionConvertorConstants;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
@@ -254,6 +256,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private IEvaluationContext externalHostServices;
private boolean noExtensibleWarnings;
private String serverBase;
+
+ private IEnableWhenEvaluator myEnableWhenEvaluator = new DefaultEnableWhenEvaluator();
/*
* Keeps track of whether a particular profile has been checked or not yet
@@ -2464,6 +2468,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
public void setAllowXsiLocation(boolean allowXsiLocation) {
this.allowXsiLocation = allowXsiLocation;
}
+
+ public void setMyEnableWhenEvaluator(IEnableWhenEvaluator myEnableWhenEvaluator) {
+ this.myEnableWhenEvaluator = myEnableWhenEvaluator;
+ }
/**
*
@@ -2734,21 +2742,21 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
sdTime = sdTime + (System.nanoTime() - t);
if (warning(errors, IssueType.REQUIRED, q.line(), q.col(), stack.getLiteralPath(), qsrc != null, "The questionnaire \""+questionnaire+"\" could not be resolved, so no validation can be performed against the base questionnaire")) {
boolean inProgress = "in-progress".equals(element.getNamedChildValue("status"));
- validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress);
+ validateQuestionannaireResponseItems(qsrc, qsrc.getItem(), errors, element, stack, inProgress, element);
}
}
}
- private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List errors, Element element, NodeStack stack, boolean inProgress) {
+ private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot) {
String text = element.getNamedChildValue("text");
rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), Utilities.noString(text) || text.equals(qItem.getText()), "If text exists, it must match the questionnaire definition for linkId "+qItem.getLinkId());
List answers = new ArrayList();
element.getNamedChildren("answer", answers);
if (inProgress)
- warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
+ warning(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), isAnswerRequirementFulfilled(qItem, answers), "No response answer found for required item "+qItem.getLinkId());
else
- rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), (answers.size() > 0) || !qItem.getRequired(), "No response answer found for required item "+qItem.getLinkId());
+ rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), isAnswerRequirementFulfilled(qItem, answers), "No response answer found for required item "+qItem.getLinkId());
if (answers.size() > 1)
rule(errors, IssueType.INVALID, answers.get(1).line(), answers.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response answer item with this linkId allowed");
@@ -2823,7 +2831,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// no validation
break;
}
- validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress);
+ validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, answer, stack, inProgress, questionnaireResponseRoot);
}
if (qItem.getType() == null) {
fail(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), false, "Definition for item "+qItem.getLinkId() + " does not contain a type");
@@ -2832,16 +2840,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
element.getNamedChildren("item", items);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), items.isEmpty(), "Items not of type DISPLAY should not have items - linkId {0}", qItem.getLinkId());
} else {
- validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress);
+ validateQuestionannaireResponseItems(qsrc, qItem.getItem(), errors, element, stack, inProgress, questionnaireResponseRoot);
}
}
- private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List errors, List elements, NodeStack stack, boolean inProgress) {
+private boolean isAnswerRequirementFulfilled(QuestionnaireItemComponent qItem, List answers) {
+ return !answers.isEmpty() || !qItem.getRequired() || qItem.getType() == QuestionnaireItemType.GROUP;
+}
+
+ private void validateQuestionannaireResponseItem(Questionnaire qsrc, QuestionnaireItemComponent qItem, List errors, List elements, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot) {
if (elements.size() > 1)
rule(errors, IssueType.INVALID, elements.get(1).line(), elements.get(1).col(), stack.getLiteralPath(), qItem.getRepeats(), "Only one response item with this linkId allowed - " + qItem.getLinkId());
for (Element element : elements) {
NodeStack ns = stack.push(element, -1, null, null);
- validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
+ validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress, questionnaireResponseRoot);
}
}
@@ -2852,8 +2864,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
return -1;
}
-
- private void validateQuestionannaireResponseItems(Questionnaire qsrc, List qItems, List errors, Element element, NodeStack stack, boolean inProgress) {
+
+ private void validateQuestionannaireResponseItems(Questionnaire qsrc, List qItems, List errors, Element element, NodeStack stack, boolean inProgress, Element questionnaireResponseRoot) {
List items = new ArrayList();
element.getNamedChildren("item", items);
// now, sort into stacks
@@ -2866,9 +2878,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (index == -1) {
QuestionnaireItemComponent qItem = findQuestionnaireItem(qsrc, linkId);
if (qItem != null) {
- rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, "Structural Error: item is in the wrong place");
+ rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index > -1, misplacedItemError(qItem));
NodeStack ns = stack.push(item, -1, null, null);
- validateQuestionannaireResponseItem(qsrc, qItem, errors, element, ns, inProgress);
+ validateQuestionannaireResponseItem(qsrc, qItem, errors, item, ns, inProgress, questionnaireResponseRoot);
}
else
rule(errors, IssueType.NOTFOUND, item.line(), item.col(), stack.getLiteralPath(), index > -1, "LinkId \""+linkId+"\" not found in questionnaire");
@@ -2877,11 +2889,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
{
rule(errors, IssueType.STRUCTURE, item.line(), item.col(), stack.getLiteralPath(), index >= lastIndex, "Structural Error: items are out of order");
lastIndex = index;
- List mapItem = map.get(linkId);
- if (mapItem == null) {
- mapItem = new ArrayList();
- map.put(linkId, mapItem);
- }
+
+ List mapItem = map.computeIfAbsent(linkId, key -> new ArrayList<>());
+
mapItem.add(item);
}
}
@@ -2890,13 +2900,25 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// ok, now we have a list of known items, grouped by linkId. We"ve made an error for anything out of order
for (QuestionnaireItemComponent qItem : qItems) {
List mapItem = map.get(qItem.getLinkId());
- if (mapItem != null)
- validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress);
- else
- rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
+ if (mapItem != null){
+ rule(errors, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(), myEnableWhenEvaluator.isQuestionEnabled(qItem, questionnaireResponseRoot), "Item has answer, even though it is not enabled "+qItem.getLinkId());
+ validateQuestionannaireResponseItem(qsrc, qItem, errors, mapItem, stack, inProgress, questionnaireResponseRoot);
+ } else {
+ //item is missing, is the question enabled?
+ if (myEnableWhenEvaluator.isQuestionEnabled(qItem, questionnaireResponseRoot)) {
+ rule(errors, IssueType.REQUIRED, element.line(), element.col(), stack.getLiteralPath(), !qItem.getRequired(), "No response found for required item "+qItem.getLinkId());
+ }
+ }
}
}
+private String misplacedItemError(QuestionnaireItemComponent qItem) {
+ return qItem.hasLinkId() ?
+ String.format("Structural Error: item with linkid %s is in the wrong place", qItem.getLinkId())
+ :
+ "Structural Error: item is in the wrong place";
+}
+
private void validateQuestionnaireResponseItemQuantity( List errors, Element answer, NodeStack stack) {
}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/EnableWhenResult.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/EnableWhenResult.java
new file mode 100644
index 00000000000..8568fbe271e
--- /dev/null
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/EnableWhenResult.java
@@ -0,0 +1,47 @@
+package org.hl7.fhir.dstu3.hapi.validation;
+
+import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
+import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
+
+public class EnableWhenResult {
+ private final boolean enabled;
+ private final QuestionnaireItemEnableWhenComponent enableWhenCondition;
+ private final QuestionnaireResponseItemComponent responseItem;
+ private final String linkId;
+
+ /**
+ * Evaluation result of enableWhen condition
+ *
+ * @param enabled
+ * Evaluation result
+ * @param linkId
+ * LinkId of the questionnaire item
+ * @param enableWhenCondition
+ * Evaluated enableWhen condition
+ * @param responseItem
+ * item in QuestionnaireResponse
+ */
+ public EnableWhenResult(boolean enabled, String linkId, QuestionnaireItemEnableWhenComponent enableWhenCondition,
+ QuestionnaireResponseItemComponent responseItem) {
+ this.enabled = enabled;
+ this.linkId = linkId;
+ this.responseItem = responseItem;
+ this.enableWhenCondition = enableWhenCondition;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public String getLinkId() {
+ return linkId;
+ }
+
+ public QuestionnaireResponseItemComponent getResponseItem() {
+ return responseItem;
+ }
+
+ public QuestionnaireItemEnableWhenComponent getEnableWhenCondition() {
+ return enableWhenCondition;
+ }
+}
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java
index 32e77320c39..e50b14f40bc 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/dstu3/hapi/validation/QuestionnaireResponseValidatorDstu3Test.java
@@ -7,35 +7,37 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
-import org.apache.commons.io.IOUtils;
+
import org.hamcrest.Matchers;
-import org.hl7.fhir.dstu3.context.IWorkerContext;
-import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport.CodeValidationResult;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemComponent;
+import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemEnableWhenComponent;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemOptionComponent;
import org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType;
+import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemAnswerComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
-import java.util.stream.Collectors;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasSize;
+import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.BOOLEAN;
+import static org.hl7.fhir.dstu3.model.Questionnaire.QuestionnaireItemType.CHOICE;
+import static org.hl7.fhir.dstu3.model.QuestionnaireResponse.QuestionnaireResponseStatus.COMPLETED;
import static org.junit.Assert.*;
-import static org.mockito.ArgumentMatchers.contains;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
@@ -43,23 +45,26 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
public class QuestionnaireResponseValidatorDstu3Test {
+ private static final String QUESTIONNAIRE_URL = "http://example.com/Questionnaire/q1";
public static final IdType ID_ICC_QUESTIONNAIRE_SETUP = new IdType("Questionnaire/profile");
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorDstu3Test.class);
private static final String CODE_ICC_SCHOOLTYPE_PT = "PT";
private static final IdType ID_VS_SCHOOLTYPE = new IdType("ValueSet/schooltype");
private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype";
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
- private static FhirContext ourCtx = FhirContext.forDstu3();
+ private static FhirContext ourCtx;
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
private IValidationSupport myValSupport;
- private IWorkerContext myWorkerCtx;
-
+
+ @BeforeClass
+ public static void beforeClass() {
+ ourCtx = FhirContext.forDstu3();
+ }
+
@Before
public void before() {
myValSupport = mock(IValidationSupport.class);
- // new DefaultProfileValidationSupport();
- myWorkerCtx = new HapiWorkerContext(ourCtx, myValSupport);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
@@ -128,7 +133,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
answerValues[11] = new Coding().setSystem("http://codesystems.com/system").setCode("code0");
answerValues[12] = new StringType("some value");
answerValues[13] = new Attachment().setData("some data".getBytes()).setContentType("txt");
- answerValues[14] = new Reference("http://example.com/Questionnaire/q1");
+ answerValues[14] = new Reference(QUESTIONNAIRE_URL);
answerValues[15] = new Quantity(42);
for (int i = 0; i < itemCnt; i++) {
@@ -138,7 +143,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
reset(myValSupport);
Questionnaire q = new Questionnaire();
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class),
- eq("http://example.com/Questionnaire/q1"))).thenReturn(q);
+ eq(QUESTIONNAIRE_URL))).thenReturn(q);
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
myInstanceVal.flushCaches();
@@ -155,7 +160,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
qa.addItem().setLinkId(linkId).addAnswer().setValue(answerValues[i]);
ValidationResult errors = myVal.validateWithResult(qa);
@@ -171,7 +176,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
@@ -184,11 +189,11 @@ public class QuestionnaireResponseValidatorDstu3Test {
@Test
public void testCodedAnswer() {
- String questionnaireRef = "http://example.com/Questionnaire/q1";
+ String questionnaireRef = QUESTIONNAIRE_URL;
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setOptions(new Reference("http://somevalueset"));
- when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q);
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(QUESTIONNAIRE_URL))).thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
@@ -252,7 +257,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
QuestionnaireResponseItemComponent qaGroup = qa.addItem();
qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
@@ -262,6 +267,43 @@ public class QuestionnaireResponseValidatorDstu3Test {
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No LinkId, so can't be validated"));
}
+
+ @Test
+ public void testMissingAnswerInNestedStructureIsReported() throws Exception {
+ Questionnaire q = new Questionnaire();
+ q.addItem().setType(QuestionnaireItemType.GROUP).setRequired(true)
+ .addItem().setType(QuestionnaireItemType.GROUP).setRequired(true)
+ .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("link0").setRequired(true);
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ assertThat(errors.toString(), Matchers.not(containsString("No issues")));
+ }
+
+ @Test
+ public void testGroupMarkedAsRequiredIsOk() throws Exception {
+ Questionnaire q = new Questionnaire();
+ q.addItem().setType(QuestionnaireItemType.GROUP).setRequired(true).setLinkId("link1")
+ .addItem().setType(QuestionnaireItemType.BOOLEAN).setLinkId("link0").setRequired(true);
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ qa.addItem().setLinkId("link1")
+ .addItem().setLinkId("link0").addAnswer().setValue(new BooleanType(true));
+
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ assertThat(errors.toString(), containsString("No issues"));
+ }
@Test
public void testItemWithNoType() {
@@ -272,7 +314,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
QuestionnaireResponseItemComponent qaGroup = qa.addItem().setLinkId("link0");
qaGroup.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
@@ -293,7 +335,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
String reference = qa.getQuestionnaire().getReference();
@@ -303,10 +345,271 @@ public class QuestionnaireResponseValidatorDstu3Test {
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No response found for required item link0"));
}
+
+ @Test
+ public void testEnableWhenWithHasAnswerTrueDisablesQuestionWhenNoAnswerIsPresent() {
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING);
+ q.addItem().setLinkId("link1").setRequired(true).addEnableWhen().setQuestion("link0").setHasAnswer(true);
+
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
+ @Test
+ public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionHasAnswerTrue() {
+
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY);
+
+ //link1 question is enabled when link0 has answer
+ QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
+ item1.setLinkId("link1").setRequired(true);
+ q.addItem(item1);
+ QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent();
+ item1.addEnableWhen(enable);
+ enable.setQuestion("link0");
+ enable.setHasAnswer(true);
+ enable.setAnswer(new Quantity().setValue(1L));
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
+ @Test
+ public void testRequiredQuestionQuantityWithEnableWhenHidesQuestionValue() {
+
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY);
+
+ //link1 question is enabled when link0 has answer
+ QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
+ item1.setLinkId("link1").setRequired(true);
+ q.addItem(item1);
+ QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent();
+ item1.addEnableWhen(enable);
+ enable.setQuestion("link0");
+ enable.setAnswer(new Quantity().setValue(2L));
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L));
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
+ @Test
+ public void testRequiredQuestionQuantityWithEnableWhenEnablesQuestionValue() {
+
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.QUANTITY);
+
+ //link1 question is enabled when link0 has answer
+ QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
+ item1.setLinkId("link1").setRequired(true);
+ q.addItem(item1);
+ QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent();
+ item1.addEnableWhen(enable);
+ enable.setQuestion("link0");
+ enable.setAnswer(new Quantity().setValue(1L));
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ qa.addItem().setLinkId("link0").addAnswer().setValue(new Quantity().setValue(1L));
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No response found for required item link1"));
+ }
+
+ @Test
+ public void testRequiredQuestionWithEnableWhenHasAnswerTrueWithAnswer() {
+
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.STRING);
+
+ // create the questionnaire
+ QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
+ item1.setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING);
+ q.addItem(item1);
+ QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent();
+ item1.addEnableWhen(enable);
+ enable.setQuestion("link0");
+ enable.setHasAnswer(true);
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
+ qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("BAR"));
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
+
+ @Test
+ public void testRequiredQuestionWithEnableWheHidesRequiredQuestionnHasAnswerFalse() {
+
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING);
+
+ // create the questionnaire
+ QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
+ item1.setLinkId("link1").setRequired(true);
+ q.addItem(item1);
+ QuestionnaireItemEnableWhenComponent enable = new QuestionnaireItemEnableWhenComponent();
+ item1.addEnableWhen(enable);
+ enable.setQuestion("link0");
+ enable.setHasAnswer(false);
+
+ QuestionnaireResponse qa = new QuestionnaireResponse();
+ qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+
+ // link1 should be disabled, because the enableWhen enables it when link0 doesn't haven an answer
+ qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
+
+ String reference = qa.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+ ValidationResult errors = myVal.validateWithResult(qa);
+
+ ourLog.info(errors.toString());
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
+ @Test
+ public void testGivenQuestionIsNotEnabledWithEnableWhenAnswersAreReportedAsErrors() throws Exception {
+ Questionnaire q = new Questionnaire();
+ q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.STRING);
+ q.addItem().setLinkId("link2").setRequired(false).setType(QuestionnaireItemType.STRING).addEnableWhen().setQuestion("link0").setHasAnswer(true);
+
+ QuestionnaireResponse qr = new QuestionnaireResponse();
+ qr.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+
+ qr.addItem().setLinkId("link2").addAnswer().setValue(new StringType("FOO"));
+
+ String reference = qr.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qr);
+
+ assertThat(errors.toString(), Matchers.not(containsString("No issues")));
+ }
+
+ @Test
+ public void testGivenQuestionnaireResponseHasSiblingItemsWhenTheyShouldBeChildItems() throws Exception {
+ Questionnaire q = new Questionnaire();
+ QuestionnaireItemComponent item = q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.GROUP);
+ item.addItem().setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING);
+
+ QuestionnaireResponse qr = new QuestionnaireResponse();
+ qr.setStatus(QuestionnaireResponseStatus.COMPLETED);
+ qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ qr.addItem().setLinkId("link0").setText("Text");
+ qr.addItem().setLinkId("link1").addAnswer().setValue(new StringType("Answer"));
+ String reference = qr.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qr);
+ assertThat(errors.toString(), Matchers.not(containsString("No issues")));
+ assertTrue("Must contain structural error about misplaced link1 item",
+ errors.getMessages().stream().filter(vm -> vm.getMessage().contains("Structural Error"))
+ .anyMatch(vm -> vm.getMessage().contains("link1")));
+ }
+
+ @Test
+ public void testAnswerIsValueCodingWithExtensionInside() throws Exception {
+ Questionnaire q = new Questionnaire();
+ Coding qcoding = new Coding();
+ qcoding.setCode("1293");
+ q.addItem().setLinkId("1B").setRequired(true).setType(CHOICE).addOption().setValue(qcoding);
+ q.addItem().setLinkId("2B").setType(BOOLEAN).addEnableWhen().setQuestion("1B").setAnswer(qcoding);
+
+ QuestionnaireResponse qr = new QuestionnaireResponse();
+ qr.setStatus(COMPLETED);
+ qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ QuestionnaireResponseItemComponent qrItem = qr.addItem().setLinkId("1B");
+ Coding coding = new Coding();
+ coding.setCode("1293");
+ QuestionnaireResponseItemAnswerComponent answer = qrItem.addAnswer();
+ answer.setValue(coding);
+ coding.addExtension("http://hl7.org/fhir/StructureDefinition/questionnaire-hidden", new BooleanType(true));
+ qr.addItem().setLinkId("2B").addAnswer().setValue(new BooleanType(true));
+
+ String reference = qr.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qr);
+ assertThat(errors.toString(), containsString("No issues"));
+
+ }
+
+ @Test
+ public void testChoiceItemsEnableWhenHasNoSystemYetAnswerHasSystem() throws Exception {
+ Questionnaire q = new Questionnaire();
+ Coding qcoding = new Coding();
+ qcoding.setCode("male");
+ qcoding.setSystem("http://hl7.org/fhir/administrative-gender");
+ q.addItem().setLinkId("1B").setRequired(true).setType(CHOICE).addOption().setValue(qcoding);
+ Coding enablewhenCoding = new Coding();
+ enablewhenCoding.setCode("male");
+ q.addItem().setLinkId("2B").setType(BOOLEAN).addEnableWhen().setQuestion("1B").setAnswer(enablewhenCoding);
+
+ QuestionnaireResponse qr = new QuestionnaireResponse();
+ qr.setStatus(COMPLETED);
+ qr.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
+ QuestionnaireResponseItemComponent qrItem = qr.addItem().setLinkId("1B");
+ Coding coding = new Coding();
+ coding.setCode("male");
+ coding.setSystem("http://hl7.org/fhir/administrative-gender");
+ QuestionnaireResponseItemAnswerComponent answer = qrItem.addAnswer();
+ answer.setValue(coding);
+ qr.addItem().setLinkId("2B").addAnswer().setValue(new BooleanType(true));
+
+ String reference = qr.getQuestionnaire().getReference();
+ when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
+
+ ValidationResult errors = myVal.validateWithResult(qr);
+ assertThat(errors.toString(), containsString("No issues"));
+ }
+
@Test
public void testEmbeddedItemInChoice() {
- String questionnaireRef = "http://example.com/Questionnaire/q1";
+ String questionnaireRef = QUESTIONNAIRE_URL;
String valueSetRef = "http://somevalueset";
String codeSystemUrl = "http://codesystems.com/system";
String codeValue = "code0";
@@ -362,7 +665,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
@Test
public void testEmbeddedItemInOpenChoice() {
- String questionnaireRef = "http://example.com/Questionnaire/q1";
+ String questionnaireRef = QUESTIONNAIRE_URL;
String valueSetRef = "http://somevalueset";
String codeSystemUrl = "http://codesystems.com/system";
String codeValue = "code0";
@@ -418,7 +721,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
@Test
public void testEmbeddedItemInString() {
- String questionnaireRef = "http://example.com/Questionnaire/q1";
+ String questionnaireRef = QUESTIONNAIRE_URL;
// create the questionnaire
QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
@@ -519,7 +822,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
.setType(QuestionnaireItemType.STRING)
.setRequired(true);
- String reference = "http://example.com/Questionnaire/q1";
+ String reference = QUESTIONNAIRE_URL;
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference)))
.thenReturn(q);
@@ -543,7 +846,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
@Test
public void testOpenchoiceAnswer() {
- String questionnaireRef = "http://example.com/Questionnaire/q1";
+ String questionnaireRef = QUESTIONNAIRE_URL;
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent item = q.addItem();
@@ -666,7 +969,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);
@@ -684,7 +987,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
- qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
+ qa.getQuestionnaire().setReference(QUESTIONNAIRE_URL);
qa.addItem().setLinkId("link1").addItem().setLinkId("link2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire().getReference()))).thenReturn(q);