More work on QA validator
This commit is contained in:
parent
ab2129d651
commit
c49941060b
|
@ -37,12 +37,31 @@ import org.hl7.fhir.utilities.Utilities;
|
|||
import org.hl7.fhir.instance.model.annotations.Child;
|
||||
import org.hl7.fhir.instance.model.annotations.Description;
|
||||
import org.hl7.fhir.instance.model.annotations.DatatypeDef;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.annotations.Block;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
/**
|
||||
* Base definition for all elements in a resource.
|
||||
*/
|
||||
public abstract class Element extends Base implements IBaseHasExtensions {
|
||||
/**
|
||||
* Returns an unmodifiable list containing all extensions on this element which
|
||||
* match the given URL.
|
||||
*
|
||||
* @param theUrl The URL. Must not be blank or null.
|
||||
* @return an unmodifiable list containing all extensions on this element which
|
||||
* match the given URL
|
||||
*/
|
||||
public List<Extension> getExtensionsByUrl(String theUrl) {
|
||||
Validate.notBlank(theUrl, "theUrl must not be blank or null");
|
||||
ArrayList<Extension> retVal = new ArrayList<Extension>();
|
||||
for (Extension next : getExtension()) {
|
||||
if (theUrl.equals(next.getUrl())) {
|
||||
retVal.add(next);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(retVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* unique id for the element within a resource (for internal references).
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.hl7.fhir.instance.client.ResourceFormat;
|
|||
import org.hl7.fhir.instance.model.Bundle;
|
||||
import org.hl7.fhir.instance.model.ConceptMap;
|
||||
import org.hl7.fhir.instance.model.Conformance;
|
||||
import org.hl7.fhir.instance.model.DataElement;
|
||||
import org.hl7.fhir.instance.model.ElementDefinition.TypeRefComponent;
|
||||
import org.hl7.fhir.instance.model.OperationOutcome;
|
||||
import org.hl7.fhir.instance.model.Parameters;
|
||||
|
@ -52,6 +53,7 @@ public class WorkerContext implements NameResolver {
|
|||
private ITerminologyServices terminologyServices = new NullTerminologyServices();
|
||||
private IFHIRClient client = new NullClient();
|
||||
private Map<String, ValueSet> codeSystems = new HashMap<String, ValueSet>();
|
||||
private Map<String, DataElement> dataElements = new HashMap<String, DataElement>();
|
||||
private Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>();
|
||||
private Map<String, ConceptMap> maps = new HashMap<String, ConceptMap>();
|
||||
private Map<String, StructureDefinition> profiles = new HashMap<String, StructureDefinition>();
|
||||
|
@ -97,6 +99,10 @@ public class WorkerContext implements NameResolver {
|
|||
return codeSystems;
|
||||
}
|
||||
|
||||
public Map<String, DataElement> getDataElements() {
|
||||
return dataElements;
|
||||
}
|
||||
|
||||
public Map<String, ValueSet> getValueSets() {
|
||||
return valueSets;
|
||||
}
|
||||
|
|
|
@ -302,4 +302,20 @@ public class BaseValidator {
|
|||
return thePass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
|
||||
*
|
||||
* @param thePass
|
||||
* Set this parameter to <code>false</code> if the validation does not pass
|
||||
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
|
||||
*/
|
||||
protected boolean warning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
|
||||
if (!thePass) {
|
||||
String path = toPath(pathParts);
|
||||
String message = formatMessage(theMessage, theMessageArguments);
|
||||
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.WARNING));
|
||||
}
|
||||
return thePass;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,9 +15,12 @@ import org.apache.commons.lang3.Validate;
|
|||
import org.hl7.fhir.instance.model.Attachment;
|
||||
import org.hl7.fhir.instance.model.BooleanType;
|
||||
import org.hl7.fhir.instance.model.Coding;
|
||||
import org.hl7.fhir.instance.model.DataElement;
|
||||
import org.hl7.fhir.instance.model.DateTimeType;
|
||||
import org.hl7.fhir.instance.model.DateType;
|
||||
import org.hl7.fhir.instance.model.DecimalType;
|
||||
import org.hl7.fhir.instance.model.ElementDefinition;
|
||||
import org.hl7.fhir.instance.model.Extension;
|
||||
import org.hl7.fhir.instance.model.InstantType;
|
||||
import org.hl7.fhir.instance.model.IntegerType;
|
||||
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
|
||||
|
@ -42,8 +45,6 @@ import org.hl7.fhir.instance.model.ValueSet.ConceptSetComponent;
|
|||
import org.hl7.fhir.instance.model.valuesets.IssueType;
|
||||
import org.hl7.fhir.instance.utils.WorkerContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
||||
/**
|
||||
* Validates that an instance of {@link QuestionnaireAnswers} is valid against the {@link Questionnaire} that it claims to conform to.
|
||||
*
|
||||
|
@ -58,6 +59,7 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
* ****************************************************************
|
||||
*/
|
||||
|
||||
private static final List<String> EMPTY_PATH = Collections.emptyList();
|
||||
private WorkerContext myWorkerCtx;
|
||||
|
||||
public QuestionnaireAnswersValidator(WorkerContext theWorkerCtx) {
|
||||
|
@ -192,29 +194,54 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
|
||||
private void validateQuestion(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
String linkId = theQuestion.getLinkId();
|
||||
QuestionComponent question = theQuestion;
|
||||
String linkId = question.getLinkId();
|
||||
if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), "Questionnaire is invalid, question found with no link ID")) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnswerFormat type = theQuestion.getType();
|
||||
AnswerFormat type = question.getType();
|
||||
if (type == null) {
|
||||
if (theQuestion.getGroup().isEmpty()) {
|
||||
rule(theErrors, IssueType.INVALID, thePathStack, false, "Questionnaire in invalid, no type and no groups specified for question with link ID[{0}]", linkId);
|
||||
return;
|
||||
// Support old format/casing and new
|
||||
List<Extension> extensions = question.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-deReference");
|
||||
if (extensions.isEmpty()) {
|
||||
extensions = question.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-dereference");
|
||||
}
|
||||
if (extensions.isEmpty() == false) {
|
||||
if (extensions.size() > 1) {
|
||||
warning(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Questionnaire is invalid, element contains multiple extensions with URL 'questionnaire-dereference', maximum one may be contained in a single element");
|
||||
}
|
||||
return;
|
||||
/*
|
||||
* Hopefully we will implement this soon...
|
||||
*/
|
||||
|
||||
// Extension ext = extensions.get(0);
|
||||
// Reference ref = (Reference) ext.getValue();
|
||||
// DataElement de = myWorkerCtx.getDataElements().get(ref.getReference());
|
||||
// if (de.getElement().size() != 1) {
|
||||
// warning(theErrors, IssueType.BUSINESSRULE, EMPTY_PATH, false, "DataElement {0} has wrong number of elements: {1}", ref.getReference(), de.getElement().size());
|
||||
// }
|
||||
// ElementDefinition element = de.getElement().get(0);
|
||||
// question = toQuestion(element);
|
||||
} else {
|
||||
if (question.getGroup().isEmpty()) {
|
||||
rule(theErrors, IssueType.INVALID, thePathStack, false, "Questionnaire is invalid, no type and no groups specified for question with link ID[{0}]", linkId);
|
||||
return;
|
||||
}
|
||||
type = AnswerFormat.NULL;
|
||||
}
|
||||
type = AnswerFormat.NULL;
|
||||
}
|
||||
|
||||
List<org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent> answers = findAnswersByLinkId(theAnsGroup.getQuestion(), linkId);
|
||||
if (answers.size() > 1) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Multiple answers repetitions found with linkId[{0}]", linkId);
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Multiple answers repetitions found with linkId[{0}]", linkId);
|
||||
}
|
||||
if (answers.size() == 0) {
|
||||
if (theValidateRequired) {
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
} else {
|
||||
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -222,13 +249,14 @@ public class QuestionnaireAnswersValidator extends BaseValidator {
|
|||
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion = answers.get(0);
|
||||
try {
|
||||
thePathStack.add("question[" + answers.indexOf(answerQuestion) + "]");
|
||||
validateQuestionAnswers(theErrors, theQuestion, thePathStack, type, answerQuestion, theAnswers, theValidateRequired);
|
||||
validateQuestionGroups(theErrors, theQuestion, answerQuestion, thePathStack, theAnswers, theValidateRequired);
|
||||
validateQuestionAnswers(theErrors, question, thePathStack, type, answerQuestion, theAnswers, theValidateRequired);
|
||||
validateQuestionGroups(theErrors, question, answerQuestion, thePathStack, theAnswers, theValidateRequired);
|
||||
} finally {
|
||||
thePathStack.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void validateQuestionGroups(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent theAnswerQuestion,
|
||||
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers, boolean theValidateRequired) {
|
||||
validateGroups(theErrors, theQuestion.getGroup(), theAnswerQuestion.getGroup(), thePathSpec, theAnswers, theValidateRequired);
|
||||
|
|
|
@ -4,11 +4,13 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.hl7.fhir.instance.model.Coding;
|
||||
import org.hl7.fhir.instance.model.DataElement;
|
||||
import org.hl7.fhir.instance.model.Questionnaire;
|
||||
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
|
||||
import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
|
||||
|
@ -24,6 +26,7 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
|
||||
public class QuestionnaireAnswersValidatorTest {
|
||||
private static final FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
|
||||
|
@ -56,6 +59,20 @@ public class QuestionnaireAnswersValidatorTest {
|
|||
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtensionDereference() throws Exception {
|
||||
Questionnaire q = ourCtx.newJsonParser().parseResource(Questionnaire.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-q.json")));
|
||||
QuestionnaireAnswers qa = ourCtx.newXmlParser().parseResource(QuestionnaireAnswers.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-qa.xml")));
|
||||
DataElement de = ourCtx.newJsonParser().parseResource(DataElement.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-de.json")));
|
||||
|
||||
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
|
||||
myWorkerCtx.getDataElements().put("DataElement/4771", de);
|
||||
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||
myVal.validate(errors, qa);
|
||||
|
||||
ourLog.info(errors.toString());
|
||||
assertEquals(errors.toString(), errors.size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupWithNoLinkIdInQuestionnaireAnswers() {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"resourceType":"DataElement",
|
||||
"id":"4770",
|
||||
"meta":{
|
||||
"versionId":"1",
|
||||
"lastUpdated":"2015-07-09T03:28:33.831-04:00"
|
||||
},
|
||||
"text":{
|
||||
"status":"generated",
|
||||
"div":"<div xmlns=\"http://www.w3.org/1999/xhtml\"><!-- Snipped for brevity --></div>"
|
||||
},
|
||||
"identifier":{
|
||||
"value":"Age Question"
|
||||
},
|
||||
"version":"1.0",
|
||||
"name":"QuestionSample",
|
||||
"status":"active",
|
||||
"publisher":"EDIFECS",
|
||||
"element":[
|
||||
{
|
||||
"path":"DataElement.question",
|
||||
"label":"What is your age?",
|
||||
"type":[
|
||||
{
|
||||
"code":"positiveInt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"resourceType":"Questionnaire",
|
||||
"id":"4772",
|
||||
"meta":{
|
||||
"versionId":"1",
|
||||
"lastUpdated":"2015-07-09T03:30:29.589-04:00"
|
||||
},
|
||||
"identifier":[
|
||||
{
|
||||
"value":"My First Questionnaire"
|
||||
}
|
||||
],
|
||||
"date":"2015-07-09T11:32:03+04:00",
|
||||
"publisher":"Edifecs",
|
||||
"group":{
|
||||
"title":"Main Group",
|
||||
"group":[
|
||||
{
|
||||
"text":"Common",
|
||||
"question":[
|
||||
{
|
||||
"linkId":"Link0",
|
||||
"text":"What is your name?",
|
||||
"type":"string"
|
||||
},
|
||||
{
|
||||
"linkId":"Link1",
|
||||
"text":"What is your age?",
|
||||
"type":"integer"
|
||||
},
|
||||
{
|
||||
"linkId":"Link2",
|
||||
"text":"Do you smoke?",
|
||||
"type":"boolean"
|
||||
},
|
||||
{
|
||||
"extension":[
|
||||
{
|
||||
"url":"http://hl7.org/fhir/StructureDefinition/questionnaire-deReference",
|
||||
"valueReference":{
|
||||
"reference":"DataElement/4770"
|
||||
}
|
||||
}
|
||||
],
|
||||
"linkId":"Link3"
|
||||
},
|
||||
{
|
||||
"extension":[
|
||||
{
|
||||
"url":"http://hl7.org/fhir/StructureDefinition/questionnaire-deReference",
|
||||
"valueReference":{
|
||||
"reference":"DataElement/4771"
|
||||
}
|
||||
}
|
||||
],
|
||||
"linkId":"Link4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<QuestionnaireAnswers xmlns="http://hl7.org/fhir">
|
||||
<identifier>
|
||||
<value value="4772"/>
|
||||
</identifier>
|
||||
<questionnaire>
|
||||
<reference value="Questionnaire/4772"/>
|
||||
</questionnaire>
|
||||
<subject>
|
||||
<reference value="Organization/4769"/>
|
||||
</subject>
|
||||
<authored value="2015-07-29T20:39:17+04:00"/>
|
||||
<group>
|
||||
<title value="Main Group"/>
|
||||
<group>
|
||||
<text value="Common"/>
|
||||
<question>
|
||||
<linkId value="Link0"/>
|
||||
<text value="What is your name?"/>
|
||||
<answer>
|
||||
<valueString value="swewe"/>
|
||||
</answer>
|
||||
</question>
|
||||
<question>
|
||||
<linkId value="Link1"/>
|
||||
<text value="What is your age?"/>
|
||||
<answer>
|
||||
<valueInteger value="1"/>
|
||||
</answer>
|
||||
</question>
|
||||
<question>
|
||||
<linkId value="Link2"/>
|
||||
<text value="Do you smoke?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</question>
|
||||
<question>
|
||||
<linkId value="Link3"/>
|
||||
<text value="What is your age?"/>
|
||||
<answer>
|
||||
<valueInteger value="23"/>
|
||||
</answer>
|
||||
</question>
|
||||
<question>
|
||||
<linkId value="Link4"/>
|
||||
<text value="Do you smoke?"/>
|
||||
<answer>
|
||||
<valueBoolean value="false"/>
|
||||
</answer>
|
||||
</question>
|
||||
</group>
|
||||
</group>
|
||||
</QuestionnaireAnswers>
|
Loading…
Reference in New Issue