Send Accept header on client requests, and allow string responses in

QuestionnaireResponse validator for questions of type OPENCHOICE
This commit is contained in:
James Agnew 2015-09-24 13:40:07 -04:00
parent 81e7d8c071
commit 072c1ece87
13 changed files with 581 additions and 360 deletions

View File

@ -31,6 +31,7 @@ import org.apache.http.Header;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.message.BasicHeader; import org.apache.http.message.BasicHeader;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.util.VersionUtil;
@ -85,7 +86,7 @@ public abstract class BaseHttpClientInvocation {
} }
} }
public void addHeadersToRequest(HttpRequestBase theHttpRequest) { public void addHeadersToRequest(HttpRequestBase theHttpRequest, EncodingEnum theEncoding) {
if (myHeaders != null) { if (myHeaders != null) {
for (Header next : myHeaders) { for (Header next : myHeaders) {
theHttpRequest.addHeader(next); theHttpRequest.addHeader(next);
@ -96,6 +97,13 @@ public abstract class BaseHttpClientInvocation {
theHttpRequest.addHeader("Accept-Charset", "utf-8"); theHttpRequest.addHeader("Accept-Charset", "utf-8");
theHttpRequest.addHeader("Accept-Encoding", "gzip"); theHttpRequest.addHeader("Accept-Encoding", "gzip");
if (theEncoding == null) {
theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.HEADER_ACCEPT_VALUE_ALL);
} else if (theEncoding == EncodingEnum.JSON) {
theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON);
} else if (theEncoding == EncodingEnum.XML) {
theHttpRequest.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_XML);
}
} }
} }

View File

@ -916,7 +916,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
public Object execute() { public Object execute() {
ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null); ResourceResponseHandler binding = new ResourceResponseHandler(myType.getImplementingClass(), null);
HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation(); HttpGetClientInvocation invocation = MethodUtil.createConformanceInvocation();
return invokeClient(myContext, binding, invocation, myLogRequestAndResponse); return super.invoke(null, binding, invocation);
} }
@Override @Override

View File

@ -280,6 +280,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
void validateServerBase(String theServerBase, HttpClient theHttpClient, BaseClient theClient) { void validateServerBase(String theServerBase, HttpClient theHttpClient, BaseClient theClient) {
GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this); GenericClient client = new GenericClient(myContext, theHttpClient, theServerBase, this);
client.setEncoding(theClient.getEncoding());
for (IClientInterceptor interceptor : theClient.getInterceptors()) { for (IClientInterceptor interceptor : theClient.getInterceptors()) {
client.registerInterceptor(interceptor); client.registerInterceptor(interceptor);
} }

View File

@ -298,7 +298,7 @@ abstract class BaseHttpClientInvocationWithContents extends BaseHttpClientInvoca
} }
HttpRequestBase retVal = createRequest(url, entity); HttpRequestBase retVal = createRequest(url, entity);
super.addHeadersToRequest(retVal); super.addHeadersToRequest(retVal, encoding);
addMatchHeaders(retVal, url); addMatchHeaders(retVal, url);
if (contentType != null) { if (contentType != null) {

View File

@ -62,7 +62,7 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation {
appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1); appendExtraParamsWithQuestionMark(theExtraParams, b, b.indexOf("?") == -1);
HttpDelete retVal = new HttpDelete(b.toString()); HttpDelete retVal = new HttpDelete(b.toString());
super.addHeadersToRequest(retVal); super.addHeadersToRequest(retVal, theEncoding);
return retVal; return retVal;
} }

View File

@ -103,7 +103,7 @@ public class HttpGetClientInvocation extends BaseHttpClientInvocation {
appendExtraParamsWithQuestionMark(theExtraParams, b, first); appendExtraParamsWithQuestionMark(theExtraParams, b, first);
HttpGet retVal = new HttpGet(b.toString()); HttpGet retVal = new HttpGet(b.toString());
super.addHeadersToRequest(retVal); super.addHeadersToRequest(retVal, theEncoding);
return retVal; return retVal;
} }

View File

@ -40,7 +40,7 @@ public class HttpSimpleGetClientInvocation extends BaseHttpClientInvocation {
@Override @Override
public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) { public HttpRequestBase asHttpRequest(String theUrlBase, Map<String, List<String>> theExtraParams, EncodingEnum theEncoding, Boolean thePrettyPrint) {
HttpGet retVal = new HttpGet(myUrl); HttpGet retVal = new HttpGet(myUrl);
super.addHeadersToRequest(retVal); super.addHeadersToRequest(retVal, theEncoding);
return retVal; return retVal;
} }

View File

@ -54,6 +54,7 @@ public class Constants {
public static final String FORMAT_XML = "xml"; public static final String FORMAT_XML = "xml";
public static final String HEADER_ACCEPT = "Accept"; public static final String HEADER_ACCEPT = "Accept";
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
public static final String HEADER_ACCEPT_VALUE_ALL = CT_FHIR_XML + ";q=1.0, " + CT_FHIR_XML + ";q=1.0";
public static final String HEADER_ALLOW = "Allow"; public static final String HEADER_ALLOW = "Allow";
public static final String HEADER_AUTHORIZATION = "Authorization"; public static final String HEADER_AUTHORIZATION = "Authorization";
public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic "; public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic ";

View File

@ -625,7 +625,7 @@ public abstract class BaseHapiFhirResourceDao<T extends IResource> extends BaseH
resourceId = dt.getIdPart(); resourceId = dt.getIdPart();
} }
Long targetPid = translateForcedIdToPid(new IdDt(resourceId)); Long targetPid = translateForcedIdToPid(new IdDt(resourceId));
ourLog.info("Searching for resource link with target PID: {}", targetPid); ourLog.debug("Searching for resource link with target PID: {}", targetPid);
Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid); Predicate eq = builder.equal(from.get("myTargetResourcePid"), targetPid);
codePredicates.add(eq); codePredicates.add(eq);

View File

@ -77,6 +77,7 @@ public class GenericClientDstu2Test {
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
myResponseCount = 0;
} }
private String extractBody(ArgumentCaptor<HttpUriRequest> capt, int count) throws IOException { private String extractBody(ArgumentCaptor<HttpUriRequest> capt, int count) throws IOException {
@ -139,6 +140,130 @@ public class GenericClientDstu2Test {
idx++; idx++;
} }
@Test
public void testAcceptHeaderFetchConformance() throws Exception {
IParser p = ourCtx.newXmlParser();
Conformance conf = new Conformance();
conf.setCopyright("COPY");
final String respString = p.encodeResourceToString(conf);
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8"));
}
});
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0;
Conformance resp = (Conformance)client.fetchConformance().ofType(Conformance.class).execute();
assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(idx).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL));
idx++;
resp = (Conformance)client.fetchConformance().ofType(Conformance.class).encodedJson().execute();
assertEquals("http://example.com/fhir/metadata?_format=json", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(idx).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON));
idx++;
resp = (Conformance)client.fetchConformance().ofType(Conformance.class).encodedXml().execute();
assertEquals("http://example.com/fhir/metadata?_format=xml", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(idx).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(idx).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_XML));
idx++;
}
private int myResponseCount = 0;
@Test
public void testAcceptHeaderPreflightConformancePreferJson() throws Exception {
String methodName = "testAcceptHeaderPreflightConformancePreferJson";
final IParser p = ourCtx.newXmlParser();
final Conformance conf = new Conformance();
conf.setCopyright("COPY");
final Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myResponseCount++ == 0) {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(conf)), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(patient)), Charset.forName("UTF-8"));
}
}
});
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
IGenericClient client = ourCtx.newRestfulGenericClient("http://"+methodName+".example.com/fhir");
client.setEncoding(EncodingEnum.JSON);
Patient resp = client.read(Patient.class, new IdDt("123"));
assertEquals("FAMILY", resp.getName().get(0).getFamily().get(0).getValue());
assertEquals("http://"+methodName+".example.com/fhir/metadata?_format=json", capt.getAllValues().get(0).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(0).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON));
assertEquals("http://"+methodName+".example.com/fhir/Patient/123?_format=json", capt.getAllValues().get(1).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(1).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.CT_FHIR_JSON));
}
@Test
public void testAcceptHeaderPreflightConformance() throws Exception {
String methodName = "testAcceptHeaderPreflightConformance";
final IParser p = ourCtx.newXmlParser();
final Conformance conf = new Conformance();
conf.setCopyright("COPY");
final Patient patient = new Patient();
patient.addName().addFamily("FAMILY");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer<ReaderInputStream>() {
@Override
public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable {
if (myResponseCount++ == 0) {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(conf)), Charset.forName("UTF-8"));
} else {
return new ReaderInputStream(new StringReader(p.encodeResourceToString(patient)), Charset.forName("UTF-8"));
}
}
});
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
IGenericClient client = ourCtx.newRestfulGenericClient("http://"+methodName+".example.com/fhir");
Patient resp = client.read(Patient.class, new IdDt("123"));
assertEquals("FAMILY", resp.getName().get(0).getFamily().get(0).getValue());
assertEquals("http://"+methodName+".example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(0).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(0).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL));
assertEquals("http://"+methodName+".example.com/fhir/Patient/123", capt.getAllValues().get(1).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(1).getHeaders("Accept").length);
assertThat(capt.getAllValues().get(1).getHeaders("Accept")[0].getValue(), containsString(Constants.HEADER_ACCEPT_VALUE_ALL));
}
@Test @Test
public void testCreate() throws Exception { public void testCreate() throws Exception {

View File

@ -72,8 +72,15 @@ public class QuestionnaireResponseValidator extends BaseValidator {
return Collections.unmodifiableSet(retVal); return Collections.unmodifiableSet(retVal);
} }
private List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> findAnswersByLinkId( private Set<Class<? extends Type>> allowedTypes(Class<? extends Type> theClass0, Class<? extends Type> theClass1) {
List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> theQuestion, String theLinkId) { HashSet<Class<? extends Type>> retVal = new HashSet<Class<? extends Type>>();
retVal.add(theClass0);
retVal.add(theClass1);
return Collections.unmodifiableSet(retVal);
}
private List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> findAnswersByLinkId(List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> theQuestion,
String theLinkId) {
Validate.notBlank(theLinkId, "theLinkId must not be blank"); Validate.notBlank(theLinkId, "theLinkId must not be blank");
ArrayList<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> retVal = new ArrayList<QuestionnaireResponse.QuestionComponent>(); ArrayList<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> retVal = new ArrayList<QuestionnaireResponse.QuestionComponent>();
@ -85,8 +92,7 @@ public class QuestionnaireResponseValidator extends BaseValidator {
return retVal; return retVal;
} }
private List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> findGroupByLinkId( private List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> findGroupByLinkId(List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> theGroups, String theLinkId) {
List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> theGroups, String theLinkId) {
ArrayList<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> retVal = new ArrayList<QuestionnaireResponse.GroupComponent>(); ArrayList<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> retVal = new ArrayList<QuestionnaireResponse.GroupComponent>();
for (org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent next : theGroups) { for (org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent next : theGroups) {
if (theLinkId == null) { if (theLinkId == null) {
@ -100,28 +106,25 @@ public class QuestionnaireResponseValidator extends BaseValidator {
return retVal; return retVal;
} }
// protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) { // protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
// return test(errors, type, pathParts, thePass, msg, IssueSeverity.FATAL); // return test(errors, type, pathParts, thePass, msg, IssueSeverity.FATAL);
// } // }
public void validate(List<ValidationMessage> theErrors, QuestionnaireResponse theAnswers) { public void validate(List<ValidationMessage> theErrors, QuestionnaireResponse theAnswers) {
LinkedList<String> pathStack = new LinkedList<String>(); LinkedList<String> pathStack = new LinkedList<String>();
pathStack.add("QuestionnaireResponse"); pathStack.add("QuestionnaireResponse");
pathStack.add(QuestionnaireResponse.SP_QUESTIONNAIRE); pathStack.add(QuestionnaireResponse.SP_QUESTIONNAIRE);
if (!super.fail(theErrors, IssueType.INVALID, pathStack, theAnswers.hasQuestionnaire(), if (!super.fail(theErrors, IssueType.INVALID, pathStack, theAnswers.hasQuestionnaire(), "QuestionnaireResponse does not specity which questionnaire it is providing answers to")) {
"QuestionnaireResponse does not specity which questionnaire it is providing answers to")) {
return; return;
} }
Reference questionnaireRef = theAnswers.getQuestionnaire(); Reference questionnaireRef = theAnswers.getQuestionnaire();
Questionnaire questionnaire = getQuestionnaire(theAnswers, questionnaireRef); Questionnaire questionnaire = getQuestionnaire(theAnswers, questionnaireRef);
if (questionnaire == null && theErrors.size() > 0 if (questionnaire == null && theErrors.size() > 0 && theErrors.get(theErrors.size() - 1).getLevel() == IssueSeverity.FATAL) {
&& theErrors.get(theErrors.size() - 1).getLevel() == IssueSeverity.FATAL) {
return; return;
} }
if (!fail(theErrors, IssueType.INVALID, pathStack, questionnaire != null, if (!fail(theErrors, IssueType.INVALID, pathStack, questionnaire != null, "Questionnaire {0} is not found in the WorkerContext", theAnswers.getQuestionnaire().getReference())) {
"Questionnaire {0} is not found in the WorkerContext", theAnswers.getQuestionnaire().getReference())) {
return; return;
} }
@ -185,9 +188,8 @@ public class QuestionnaireResponseValidator extends BaseValidator {
return retVal; return retVal;
} }
private void validateGroup(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, private void validateGroup(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup,
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup, LinkedList<String> thePathStack, LinkedList<String> thePathStack, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
QuestionnaireResponse theAnswers, boolean theValidateRequired) {
for (org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent next : theAnsGroup.getQuestion()) { for (org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent next : theAnsGroup.getQuestion()) {
rule(theErrors, IssueType.INVALID, thePathStack, isNotBlank(next.getLinkId()), "Question found with no linkId"); rule(theErrors, IssueType.INVALID, thePathStack, isNotBlank(next.getLinkId()), "Question found with no linkId");
@ -205,11 +207,10 @@ public class QuestionnaireResponseValidator extends BaseValidator {
// Check that there are no extra answers // Check that there are no extra answers
for (int i = 0; i < theAnsGroup.getQuestion().size(); i++) { for (int i = 0; i < theAnsGroup.getQuestion().size(); i++) {
org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent nextQuestion = theAnsGroup.getQuestion() org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent nextQuestion = theAnsGroup.getQuestion().get(i);
.get(i);
thePathStack.add("question[" + i + "]"); thePathStack.add("question[" + i + "]");
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, allowedQuestions.contains(nextQuestion.getLinkId()), rule(theErrors, IssueType.BUSINESSRULE, thePathStack, allowedQuestions.contains(nextQuestion.getLinkId()), "Found answer with linkId[{0}] but this ID is not allowed at this position",
"Found answer with linkId[{0}] but this ID is not allowed at this position", nextQuestion.getLinkId()); nextQuestion.getLinkId());
thePathStack.remove(); thePathStack.remove();
} }
@ -217,21 +218,18 @@ public class QuestionnaireResponseValidator extends BaseValidator {
} }
private void validateQuestion(List<ValidationMessage> theErrors, QuestionComponent theQuestion, private void validateQuestion(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup,
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup, LinkedList<String> thePathStack, LinkedList<String> thePathStack, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
QuestionnaireResponse theAnswers, boolean theValidateRequired) {
QuestionComponent question = theQuestion; QuestionComponent question = theQuestion;
String linkId = question.getLinkId(); String linkId = question.getLinkId();
if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), "Questionnaire is invalid, question found with no link ID")) {
"Questionnaire is invalid, question found with no link ID")) {
return; return;
} }
AnswerFormat type = question.getType(); AnswerFormat type = question.getType();
if (type == null) { if (type == null) {
// Support old format/casing and new // Support old format/casing and new
List<Extension> extensions = question List<Extension> extensions = question.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-deReference");
.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-deReference");
if (extensions.isEmpty()) { if (extensions.isEmpty()) {
extensions = question.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-dereference"); extensions = question.getExtensionsByUrl("http://hl7.org/fhir/StructureDefinition/questionnaire-dereference");
} }
@ -256,27 +254,22 @@ public class QuestionnaireResponseValidator extends BaseValidator {
// question = toQuestion(element); // question = toQuestion(element);
} else { } else {
if (question.getGroup().isEmpty()) { if (question.getGroup().isEmpty()) {
rule(theErrors, IssueType.INVALID, thePathStack, false, rule(theErrors, IssueType.INVALID, thePathStack, false, "Questionnaire is invalid, no type and no groups specified for question with link ID[{0}]", linkId);
"Questionnaire is invalid, no type and no groups specified for question with link ID[{0}]", linkId);
return; return;
} }
type = AnswerFormat.NULL; type = AnswerFormat.NULL;
} }
} }
List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> answers = findAnswersByLinkId( List<org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent> answers = findAnswersByLinkId(theAnsGroup.getQuestion(), linkId);
theAnsGroup.getQuestion(), linkId);
if (answers.size() > 1) { if (answers.size() > 1) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Multiple answers repetitions found with linkId[{0}]", linkId);
"Multiple answers repetitions found with linkId[{0}]", linkId);
} }
if (answers.size() == 0) { if (answers.size() == 0) {
if (theValidateRequired) { if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
"Missing answer to required question with linkId[{0}]", linkId);
} else { } else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !question.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
"Missing answer to required question with linkId[{0}]", linkId);
} }
return; return;
} }
@ -291,24 +284,19 @@ public class QuestionnaireResponseValidator extends BaseValidator {
} }
} }
private void validateQuestionGroups(List<ValidationMessage> theErrors, QuestionComponent theQuestion, private void validateQuestionGroups(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent theAnswerQuestion,
org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent theAnswerQuestion,
LinkedList<String> thePathSpec, QuestionnaireResponse theAnswers, boolean theValidateRequired) { LinkedList<String> thePathSpec, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
for (QuestionAnswerComponent nextAnswer : theAnswerQuestion.getAnswer()) { for (QuestionAnswerComponent nextAnswer : theAnswerQuestion.getAnswer()) {
validateGroups(theErrors, theQuestion.getGroup(), nextAnswer.getGroup(), thePathSpec, theAnswers, validateGroups(theErrors, theQuestion.getGroup(), nextAnswer.getGroup(), thePathSpec, theAnswers, theValidateRequired);
theValidateRequired);
} }
} }
private void validateGroupGroups(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, private void validateGroupGroups(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup,
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent theAnsGroup, LinkedList<String> thePathSpec, LinkedList<String> thePathSpec, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
QuestionnaireResponse theAnswers, boolean theValidateRequired) { validateGroups(theErrors, theQuestGroup.getGroup(), theAnsGroup.getGroup(), thePathSpec, theAnswers, theValidateRequired);
validateGroups(theErrors, theQuestGroup.getGroup(), theAnsGroup.getGroup(), thePathSpec, theAnswers,
theValidateRequired);
} }
private void validateGroups(List<ValidationMessage> theErrors, List<GroupComponent> theQuestionGroups, private void validateGroups(List<ValidationMessage> theErrors, List<GroupComponent> theQuestionGroups, List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> theAnswerGroups,
List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> theAnswerGroups,
LinkedList<String> thePathStack, QuestionnaireResponse theAnswers, boolean theValidateRequired) { LinkedList<String> thePathStack, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
Set<String> linkIds = new HashSet<String>(); Set<String> linkIds = new HashSet<String>();
for (GroupComponent nextQuestionGroup : theQuestionGroups) { for (GroupComponent nextQuestionGroup : theQuestionGroups) {
@ -316,11 +304,9 @@ public class QuestionnaireResponseValidator extends BaseValidator {
if (!linkIds.add(nextLinkId)) { if (!linkIds.add(nextLinkId)) {
if (isBlank(nextLinkId)) { if (isBlank(nextLinkId)) {
fail(theErrors, IssueType.BUSINESSRULE, thePathStack, false, fail(theErrors, IssueType.BUSINESSRULE, thePathStack, false,
"Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with blank/missing linkId", "Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with blank/missing linkId", nextLinkId);
nextLinkId);
} else { } else {
fail(theErrors, IssueType.BUSINESSRULE, thePathStack, false, fail(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with linkId[{0}]",
"Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with linkId[{0}]",
nextLinkId); nextLinkId);
} }
} }
@ -331,16 +317,13 @@ public class QuestionnaireResponseValidator extends BaseValidator {
String linkId = nextQuestionGroup.getLinkId(); String linkId = nextQuestionGroup.getLinkId();
allowedGroups.add(linkId); allowedGroups.add(linkId);
List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> answerGroups = findGroupByLinkId( List<org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent> answerGroups = findGroupByLinkId(theAnswerGroups, linkId);
theAnswerGroups, linkId);
if (answerGroups.isEmpty()) { if (answerGroups.isEmpty()) {
if (nextQuestionGroup.getRequired()) { if (nextQuestionGroup.getRequired()) {
if (theValidateRequired) { if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", linkId);
linkId);
} else { } else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", hint(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", linkId);
linkId);
} }
} }
continue; continue;
@ -349,9 +332,7 @@ public class QuestionnaireResponseValidator extends BaseValidator {
if (nextQuestionGroup.getRepeats() == false) { if (nextQuestionGroup.getRepeats() == false) {
int index = theAnswerGroups.indexOf(answerGroups.get(1)); int index = theAnswerGroups.indexOf(answerGroups.get(1));
thePathStack.add("group[" + index + "]"); thePathStack.add("group[" + index + "]");
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Multiple repetitions of group with linkId[{0}] found at this position, but this group can not repeat", linkId);
"Multiple repetitions of group with linkId[{0}] found at this position, but this group can not repeat",
linkId);
thePathStack.removeLast(); thePathStack.removeLast();
} }
} }
@ -369,36 +350,27 @@ public class QuestionnaireResponseValidator extends BaseValidator {
idx++; idx++;
if (!allowedGroups.contains(next.getLinkId())) { if (!allowedGroups.contains(next.getLinkId())) {
thePathStack.add("group[" + idx + "]"); thePathStack.add("group[" + idx + "]");
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Group with linkId[{0}] found at this position, but this group does not exist at this position in Questionnaire",
"Group with linkId[{0}] found at this position, but this group does not exist at this position in Questionnaire",
next.getLinkId()); next.getLinkId());
thePathStack.removeLast(); thePathStack.removeLast();
} }
} }
} }
private void validateQuestionAnswers(List<ValidationMessage> theErrors, QuestionComponent theQuestion, private void validateQuestionAnswers(List<ValidationMessage> theErrors, QuestionComponent theQuestion, LinkedList<String> thePathStack, AnswerFormat type,
LinkedList<String> thePathStack, AnswerFormat type, org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent answerQuestion, QuestionnaireResponse theAnswers, boolean theValidateRequired) {
org.hl7.fhir.instance.model.QuestionnaireResponse.QuestionComponent answerQuestion,
QuestionnaireResponse theAnswers, boolean theValidateRequired) {
String linkId = theQuestion.getLinkId(); String linkId = theQuestion.getLinkId();
Set<Class<? extends Type>> allowedAnswerTypes = determineAllowedAnswerTypes(type); Set<Class<? extends Type>> allowedAnswerTypes = determineAllowedAnswerTypes(type);
if (allowedAnswerTypes.isEmpty()) { if (allowedAnswerTypes.isEmpty()) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, answerQuestion.isEmpty(), rule(theErrors, IssueType.BUSINESSRULE, thePathStack, answerQuestion.isEmpty(), "Question with linkId[{0}] has no answer type but an answer was provided", linkId);
"Question with linkId[{0}] has no answer type but an answer was provided", linkId);
} else { } else {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(answerQuestion.getAnswer().size() > 1 && !theQuestion.getRepeats()), "Multiple answers to non repeating question with linkId[{0}]",
!(answerQuestion.getAnswer().size() > 1 && !theQuestion.getRepeats()), linkId);
"Multiple answers to non repeating question with linkId[{0}]", linkId);
if (theValidateRequired) { if (theValidateRequired) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
!(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()),
"Missing answer to required question with linkId[{0}]", linkId);
} else { } else {
hint(theErrors, IssueType.BUSINESSRULE, thePathStack, hint(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
!(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()),
"Missing answer to required question with linkId[{0}]", linkId);
} }
} }
@ -409,72 +381,66 @@ public class QuestionnaireResponseValidator extends BaseValidator {
thePathStack.add("answer[" + answerIdx + "]"); thePathStack.add("answer[" + answerIdx + "]");
Type nextValue = nextAnswer.getValue(); Type nextValue = nextAnswer.getValue();
if (!allowedAnswerTypes.contains(nextValue.getClass())) { if (!allowedAnswerTypes.contains(nextValue.getClass())) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] found of type [{1}] but this is invalid for question of type [{2}]", linkId,
"Answer to question with linkId[{0}] found of type [{1}] but this is invalid for question of type [{2}]", nextValue.getClass().getSimpleName(), type.toCode());
linkId, nextValue.getClass().getSimpleName(), type.toCode());
continue; continue;
} }
// Validate choice answers // Validate choice answers
if (type == AnswerFormat.CHOICE || type == AnswerFormat.OPENCHOICE) { if (type == AnswerFormat.CHOICE || type == AnswerFormat.OPENCHOICE) {
Coding coding = (Coding) nextAnswer.getValue(); if (nextAnswer.getValue() instanceof StringType) {
if (isBlank(coding.getCode()) && isBlank(coding.getSystem()) && isBlank(coding.getSystem())) { StringType answer = (StringType) nextAnswer.getValue();
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, if (answer == null || isBlank(answer.getValueAsString())) {
"Answer to question with linkId[{0}] is of type coding, but none of code, system, and display are populated", rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is required but answer does not have a value", linkId);
linkId);
continue;
} else if (isBlank(coding.getCode()) && isBlank(coding.getSystem())) {
if (type != AnswerFormat.OPENCHOICE) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false,
"Answer to question with linkId[{0}] is of type only has a display populated (no code or system) but question does not allow {1}",
linkId, AnswerFormat.OPENCHOICE.name());
continue; continue;
} }
} else if (isBlank(coding.getCode()) || isBlank(coding.getSystem())) { } else {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, Coding coding = (Coding) nextAnswer.getValue();
"Answer to question with linkId[{0}] has a coding, but this coding does not contain a code and system (both must be present, or neither is the question allows {1})", if (isBlank(coding.getCode())) {
linkId, AnswerFormat.OPENCHOICE.name()); rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is of type {1} but coding answer does not have a code", linkId, type.name());
continue; continue;
} }
if (isBlank(coding.getSystem())) {
String optionsRef = theQuestion.getOptions().getReference(); rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is of type {1} but coding answer does not have a system", linkId, type.name());
if (isNotBlank(optionsRef)) {
ValueSet valueSet = getValueSet(theAnswers, theQuestion.getOptions());
if (valueSet == null) {
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false,
"Question with linkId[{0}] has options ValueSet[{1}] but this ValueSet can not be found", linkId,
optionsRef);
continue; continue;
} }
boolean found = false; String optionsRef = theQuestion.getOptions().getReference();
if (coding.getSystem().equals(valueSet.getCodeSystem().getSystem())) { if (isNotBlank(optionsRef)) {
for (ConceptDefinitionComponent next : valueSet.getCodeSystem().getConcept()) { ValueSet valueSet = getValueSet(theAnswers, theQuestion.getOptions());
if (coding.getCode().equals(next.getCode())) { if (valueSet == null) {
found = true; rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Question with linkId[{0}] has options ValueSet[{1}] but this ValueSet can not be found", linkId, optionsRef);
break; continue;
}
} }
}
if (!found) { boolean found = false;
for (ConceptSetComponent nextCompose : valueSet.getCompose().getInclude()) { if (coding.getSystem().equals(valueSet.getCodeSystem().getSystem())) {
if (coding.getSystem().equals(nextCompose.getSystem())) { for (ConceptDefinitionComponent next : valueSet.getCodeSystem().getConcept()) {
for (ConceptReferenceComponent next : nextCompose.getConcept()) { if (coding.getCode().equals(next.getCode())) {
if (coding.getCode().equals(next.getCode())) { found = true;
found = true; break;
break;
}
} }
} }
if (found) { }
break; if (!found) {
for (ConceptSetComponent nextCompose : valueSet.getCompose().getInclude()) {
if (coding.getSystem().equals(nextCompose.getSystem())) {
for (ConceptReferenceComponent next : nextCompose.getConcept()) {
if (coding.getCode().equals(next.getCode())) {
found = true;
break;
}
}
}
if (found) {
break;
}
} }
} }
}
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, found, rule(theErrors, IssueType.BUSINESSRULE, thePathStack, found, "Question with linkId[{0}] has answer with system[{1}] and code[{2}] but this is not a valid answer for ValueSet[{3}]",
"Question with linkId[{0}] has answer with system[{1}] and code[{2}] but this is not a valid answer for ValueSet[{3}]", linkId, coding.getSystem(), coding.getCode(), optionsRef);
linkId, coding.getSystem(), coding.getCode(), optionsRef); }
} }
} }
@ -513,7 +479,7 @@ public class QuestionnaireResponseValidator extends BaseValidator {
allowedAnswerTypes = allowedTypes(IntegerType.class); allowedAnswerTypes = allowedTypes(IntegerType.class);
break; break;
case OPENCHOICE: case OPENCHOICE:
allowedAnswerTypes = allowedTypes(Coding.class); allowedAnswerTypes = allowedTypes(Coding.class, StringType.class);
break; break;
case QUANTITY: case QUANTITY:
allowedAnswerTypes = allowedTypes(Quantity.class); allowedAnswerTypes = allowedTypes(Quantity.class);

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.validation; package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -10,6 +11,7 @@ import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.Coding; import org.hl7.fhir.instance.model.Coding;
import org.hl7.fhir.instance.model.DataElement; import org.hl7.fhir.instance.model.DataElement;
import org.hl7.fhir.instance.model.IntegerType;
import org.hl7.fhir.instance.model.Questionnaire; import org.hl7.fhir.instance.model.Questionnaire;
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat; import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
import org.hl7.fhir.instance.model.Questionnaire.GroupComponent; import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
@ -27,240 +29,347 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
public class QuestionnaireResponseValidatorTest { public class QuestionnaireResponseValidatorTest {
private static final FhirContext ourCtx = FhirContext.forDstu2Hl7Org(); private static final FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorTest.class);
private QuestionnaireResponseValidator myVal;
private WorkerContext myWorkerCtx; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorTest.class);
private QuestionnaireResponseValidator myVal;
@Before
public void before() {
myWorkerCtx = new WorkerContext();
myVal = new QuestionnaireResponseValidator(myWorkerCtx);
}
@Test
public void testAnswerWithWrongType() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
}
@Test private WorkerContext myWorkerCtx;
public void testCodedAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset"));
myWorkerCtx.getQuestionnaires().put(questionnaireRef, q);
ValueSet options = new ValueSet();
options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("urn:system2").addConcept().setCode("code2");
myWorkerCtx.getValueSets().put("http://somevalueset", options);
QuestionnaireResponse qa;
List<ValidationMessage> errors;
// Good code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
assertEquals(errors.toString(), 0, errors.size());
qa = new QuestionnaireResponse(); @Before
qa.getQuestionnaire().setReference(questionnaireRef); public void before() {
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system2").setCode("code2")); myWorkerCtx = new WorkerContext();
errors = new ArrayList<ValidationMessage>(); myVal = new QuestionnaireResponseValidator(myWorkerCtx);
myVal.validate(errors, qa); }
assertEquals(errors.toString(), 0, errors.size());
// Bad code @Test
public void testAnswerWithWrongType() {
qa = new QuestionnaireResponse(); Questionnaire q = new Questionnaire();
qa.getQuestionnaire().setReference(questionnaireRef); q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset]"));
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system2").setCode("code3"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Question with linkId[link0] has answer with system[urn:system2] and code[code3] but this is not a valid answer for ValueSet[http://somevalueset]"));
}
@Test
public void testExtensionDereference() throws Exception {
Questionnaire q = ourCtx.newJsonParser().parseResource(Questionnaire.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-q.json")));
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-qr.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 QuestionnaireResponse qa = new QuestionnaireResponse();
public void testGroupWithNoLinkIdInQuestionnaireResponse() { qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
Questionnaire q = new Questionnaire(); qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
GroupComponent qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent qaGroup = qa.getGroup().addGroup();
qaGroup.addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
}
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
@Test myVal.validate(errors, qa);
public void testMissingRequiredQuestion() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.STRING);
q.getGroup().addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.STRING);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Missing answer to required question with linkId[link0]"));
}
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
}
@Test @Test
public void testMultipleGroupsWithNoLinkIdInQuestionnaire() { public void testCodedAnswer() {
Questionnaire q = new Questionnaire(); String questionnaireRef = "http://example.com/Questionnaire/q1";
GroupComponent qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent qaGroup = qa.getGroup().addGroup();
qaGroup.addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("ValidationMessage[level=FATAL,type=BUSINESSRULE,location=//QuestionnaireResponse/group[0],message=Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with blank/missing linkId]"));
assertEquals(1, errors.size());
}
@Test Questionnaire q = new Questionnaire();
public void testUnexpectedAnswer() { q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset"));
Questionnaire q = new Questionnaire(); myWorkerCtx.getQuestionnaires().put(questionnaireRef, q);
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]"));
assertThat(errors.toString(), containsString("message=Found answer with linkId[link1] but this ID is not allowed at this position"));
}
@Test
public void testUnexpectedGroup() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addGroup().setLinkId("link1");
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/group[0]"));
assertThat(errors.toString(), containsString("Group with linkId[link1] found at this position, but this group does not exist at this position in Questionnaire"));
}
// @Test ValueSet options = new ValueSet();
public void validateHealthConnexExample() throws Exception { options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
String input = IOUtils.toString(QuestionnaireResponseValidatorTest.class.getResourceAsStream("/questionnaireanswers-0f431c50ddbe4fff8e0dd6b7323625fc.xml")); options.getCompose().addInclude().setSystem("urn:system2").addConcept().setCode("code2");
myWorkerCtx.getValueSets().put("http://somevalueset", options);
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input); QuestionnaireResponse qa;
ArrayList<ValidationMessage> errors = new ArrayList<ValidationMessage>(); List<ValidationMessage> errors;
myVal.validate(errors, qa);
assertEquals(errors.toString(), 0, errors.size()); // Good code
/* qa = new QuestionnaireResponse();
* Now change a coded value qa.getQuestionnaire().setReference(questionnaireRef);
*/ qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
//@formatter:off errors = new ArrayList<ValidationMessage>();
input = input.replaceAll("<answer>\n" + myVal.validate(errors, qa);
" <valueCoding>\n" + assertEquals(errors.toString(), 0, errors.size());
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" +
" <code value=\"2\"/>\n" + qa = new QuestionnaireResponse();
" <display value=\"Once/twice\"/>\n" + qa.getQuestionnaire().setReference(questionnaireRef);
" </valueCoding>\n" + qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system2").setCode("code2"));
" </answer>", "<answer>\n" + errors = new ArrayList<ValidationMessage>();
" <valueCoding>\n" + myVal.validate(errors, qa);
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" + assertEquals(errors.toString(), 0, errors.size());
" <code value=\"GGG\"/>\n" +
" <display value=\"Once/twice\"/>\n" + // Bad code
" </valueCoding>\n" +
" </answer>"); qa = new QuestionnaireResponse();
assertThat(input, containsString("GGG")); qa.getQuestionnaire().setReference(questionnaireRef);
//@formatter:on qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
errors = new ArrayList<ValidationMessage>();
qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input); myVal.validate(errors, qa);
errors = new ArrayList<ValidationMessage>(); ourLog.info(errors.toString());
myVal.validate(errors, qa); assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertEquals(errors.toString(), 10, errors.size()); assertThat(errors.toString(),
} containsString("message=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset]"));
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system2").setCode("code3"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(),
containsString("message=Question with linkId[link0] has answer with system[urn:system2] and code[code3] but this is not a valid answer for ValueSet[http://somevalueset]"));
}
@Test
public void testOpenchoiceAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.OPENCHOICE).setOptions(new Reference("http://somevalueset"));
myWorkerCtx.getQuestionnaires().put(questionnaireRef, q);
ValueSet options = new ValueSet();
options.getCodeSystem().setSystem("urn:system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("urn:system2").addConcept().setCode("code2");
myWorkerCtx.getValueSets().put("http://somevalueset", options);
QuestionnaireResponse qa;
List<ValidationMessage> errors;
// Good code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
assertEquals(errors.toString(), 0, errors.size());
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system2").setCode("code2"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
assertEquals(errors.toString(), 0, errors.size());
// Bad code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(),
containsString("message=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset]"));
// Partial code
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem(null).setCode("code1"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Answer to question with linkId[link0] is of type OPENCHOICE but coding answer does not have a system"));
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("").setCode("code1"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Answer to question with linkId[link0] is of type OPENCHOICE but coding answer does not have a system"));
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("system").setCode(null));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Answer to question with linkId[link0] is of type OPENCHOICE but coding answer does not have a code"));
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("system").setCode(null));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Answer to question with linkId[link0] is of type OPENCHOICE but coding answer does not have a code"));
// Wrong type
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new IntegerType(123));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("message=Answer to question with linkId[link0] found of type [IntegerType] but this is invalid for question of type [open-choice]"));
// String answer
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("Hello"));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors, empty());
// Missing String answer
qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference(questionnaireRef);
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType(""));
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]/answer[0]"));
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] is required but answer does not have a value"));
}
@Test
public void testExtensionDereference() throws Exception {
Questionnaire q = ourCtx.newJsonParser().parseResource(Questionnaire.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-q.json")));
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, IOUtils.toString(getClass().getResourceAsStream("/dereference-qr.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 testGroupWithNoLinkIdInQuestionnaireResponse() {
Questionnaire q = new Questionnaire();
GroupComponent qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent qaGroup = qa.getGroup().addGroup();
qaGroup.addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
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 testMissingRequiredQuestion() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.STRING);
q.getGroup().addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.STRING);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Missing answer to required question with linkId[link0]"));
}
@Test
public void testMultipleGroupsWithNoLinkIdInQuestionnaire() {
Questionnaire q = new Questionnaire();
GroupComponent qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
qGroup = q.getGroup().addGroup();
qGroup.addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
org.hl7.fhir.instance.model.QuestionnaireResponse.GroupComponent qaGroup = qa.getGroup().addGroup();
qaGroup.addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString(
"ValidationMessage[level=FATAL,type=BUSINESSRULE,location=//QuestionnaireResponse/group[0],message=Questionnaire in invalid, unable to validate QuestionnaireResponse: Multiple groups found at this position with blank/missing linkId]"));
assertEquals(1, errors.size());
}
@Test
public void testUnexpectedAnswer() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/question[0]"));
assertThat(errors.toString(), containsString("message=Found answer with linkId[link1] but this ID is not allowed at this position"));
}
@Test
public void testUnexpectedGroup() {
Questionnaire q = new Questionnaire();
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
qa.getGroup().addGroup().setLinkId("link1");
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("location=//QuestionnaireResponse/group[0]/group[0]"));
assertThat(errors.toString(), containsString("Group with linkId[link1] found at this position, but this group does not exist at this position in Questionnaire"));
}
// @Test
public void validateHealthConnexExample() throws Exception {
String input = IOUtils.toString(QuestionnaireResponseValidatorTest.class.getResourceAsStream("/questionnaireanswers-0f431c50ddbe4fff8e0dd6b7323625fc.xml"));
QuestionnaireResponse qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input);
ArrayList<ValidationMessage> errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
assertEquals(errors.toString(), 0, errors.size());
/*
* Now change a coded value
*/
// @formatter:off
input = input.replaceAll(
"<answer>\n" + " <valueCoding>\n" + " <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" + " <code value=\"2\"/>\n"
+ " <display value=\"Once/twice\"/>\n" + " </valueCoding>\n" + " </answer>",
"<answer>\n" + " <valueCoding>\n" + " <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" + " <code value=\"GGG\"/>\n"
+ " <display value=\"Once/twice\"/>\n" + " </valueCoding>\n" + " </answer>");
assertThat(input, containsString("GGG"));
// @formatter:on
qa = ourCtx.newXmlParser().parseResource(QuestionnaireResponse.class, input);
errors = new ArrayList<ValidationMessage>();
myVal.validate(errors, qa);
assertEquals(errors.toString(), 10, errors.size());
}
} }

View File

@ -49,6 +49,17 @@
Narrative generator did not include OperationOutcome.issue.diagnostics in the Narrative generator did not include OperationOutcome.issue.diagnostics in the
generated narrative. generated narrative.
</action> </action>
<action type="add">
Clients (generic and annotation) did not populate the Accept header on outgoing
requests. This is now populated to indicate that the client supports both XML and
JSON unless the user has explicitly requested one or the other (in which case the
appropriate type only will be send in the accept header). Thanks to
Avinash Shanbhag for reporting!
</action>
<action type="add">
QuestionnaireResponse validator now allows responses to questions of
type OPENCHOICE to be of type 'string'
</action>
</release> </release>
<release version="1.2" date="2015-09-18"> <release version="1.2" date="2015-09-18">
<action type="add"> <action type="add">