Send Accept header on client requests, and allow string responses in
QuestionnaireResponse validator for questions of type OPENCHOICE
This commit is contained in:
parent
81e7d8c071
commit
072c1ece87
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 ";
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in New Issue