Allow validator exceptions to be ignored via confoguration

This commit is contained in:
jamesagnew 2016-05-03 21:58:09 -04:00
parent 804b271764
commit 244cad6224
6 changed files with 315 additions and 8 deletions

View File

@ -33,6 +33,8 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.OperationOutcomeUtil;
import ca.uhn.fhir.validation.FhirValidator;
@ -61,12 +63,15 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
private Integer myAddResponseIssueHeaderOnSeverity = null;
private Integer myAddResponseOutcomeHeaderOnSeverity = null;
private Integer myFailOnSeverity = ResultSeverityEnum.ERROR.ordinal();
private boolean myIgnoreValidatorExceptions;
private int myMaximumHeaderLength = 200;
private String myResponseIssueHeaderName = provideDefaultResponseHeaderName();
private String myResponseIssueHeaderValue = DEFAULT_RESPONSE_HEADER_VALUE;
private String myResponseIssueHeaderValueNoIssues = null;
private String myResponseOutcomeHeaderName = provideDefaultResponseHeaderName();
private List<IValidatorModule> myValidatorModules;
private void addResponseIssueHeader(RequestDetails theRequestDetails, SingleValidationMessage theNext) {
// Perform any string substitutions from the message format
StrLookup<?> lookup = new MyLookup(theNext);
@ -78,6 +83,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
theRequestDetails.getResponse().addHeader(myResponseIssueHeaderName, headerValue);
}
public BaseValidatingInterceptor<T> addValidatorModule(IValidatorModule theModule) {
Validate.notNull(theModule, "theModule must not be null");
if (getValidatorModules() == null) {
@ -105,7 +111,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
public ResultSeverityEnum getAddResponseOutcomeHeaderOnSeverity() {
return myAddResponseOutcomeHeaderOnSeverity != null ? ResultSeverityEnum.values()[myAddResponseOutcomeHeaderOnSeverity] : null;
}
/**
* The maximum length for an individual header. If an individual header would be written exceeding this length,
* the header value will be truncated.
@ -125,6 +131,18 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
return myValidatorModules;
}
/**
* If set to <code>true</code> (default is <code>false</code>) this interceptor
* will exit immediately and allow processing to continue if the validator throws
* any exceptions.
* <p>
* This setting is mostly useful in testing situations
* </p>
*/
public boolean isIgnoreValidatorExceptions() {
return myIgnoreValidatorExceptions;
}
abstract String provideDefaultResponseHeaderName();
/**
@ -155,6 +173,18 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
myFailOnSeverity = theSeverity != null ? theSeverity.ordinal() : null;
}
/**
* If set to <code>true</code> (default is <code>false</code>) this interceptor
* will exit immediately and allow processing to continue if the validator throws
* any exceptions.
* <p>
* This setting is mostly useful in testing situations
* </p>
*/
public void setIgnoreValidatorExceptions(boolean theIgnoreValidatorExceptions) {
myIgnoreValidatorExceptions = theIgnoreValidatorExceptions;
}
/**
* The maximum length for an individual header. If an individual header would be written exceeding this length,
* the header value will be truncated. Value must be greater than 100.
@ -218,7 +248,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
/**
* Sets the header value to add when no issues are found at or exceeding the
* threshold specified by {@link #setAddResponseHeaderOnSeverity(ResultSeverityEnum)}
* threshold specified by {@link #setAddResponseHeaderOnSeverity(ResultSeverityEnum)}
*/
public void setResponseHeaderValueNoIssues(String theResponseHeaderValueNoIssues) {
myResponseIssueHeaderValueNoIssues = theResponseHeaderValueNoIssues;
@ -247,8 +277,20 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
if (theRequest == null) {
return;
}
ValidationResult validationResult = doValidate(validator, theRequest);
ValidationResult validationResult;
try {
validationResult = doValidate(validator, theRequest);
} catch (Exception e) {
if (myIgnoreValidatorExceptions) {
ourLog.warn("Validator threw an exception during validaiton", e);
return;
}
if (e instanceof BaseServerResponseException) {
throw (BaseServerResponseException)e;
}
throw new InternalErrorException(e);
}
if (myAddResponseIssueHeaderOnSeverity != null) {
boolean found = false;
@ -273,7 +315,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
}
}
}
if (myAddResponseOutcomeHeaderOnSeverity != null) {
IBaseOperationOutcome outcome = null;
for (SingleValidationMessage next : validationResult.getMessages()) {
@ -287,7 +329,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
outcome = OperationOutcomeUtil.newInstance(ctx);
OperationOutcomeUtil.addIssue(ctx, outcome, "information", "No issues detected", "", "informational");
}
if (outcome != null) {
IParser parser = theRequestDetails.getServer().getFhirContext().newJsonParser().setPrettyPrint(false);
String encoded = parser.encodeResourceToString(outcome);
@ -297,7 +339,7 @@ abstract class BaseValidatingInterceptor<T> extends InterceptorAdapter {
theRequestDetails.getResponse().addHeader(myResponseOutcomeHeaderName, encoded);
}
}
}
private static class MyLookup extends StrLookup<String> {

View File

@ -96,4 +96,5 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri
return theValidator.validateWithResult(theRequest);
}
}

View File

@ -104,6 +104,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
requestValidator.setAddResponseHeaderOnSeverity(null);
requestValidator.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
requestValidator.addValidatorModule(instanceValidatorDstu3());
requestValidator.setIgnoreValidatorExceptions(true);
return requestValidator;
}
@ -130,6 +131,8 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
responseValidator.addExcludeOperationType(RestOperationTypeEnum.SEARCH_SYSTEM);
responseValidator.addExcludeOperationType(RestOperationTypeEnum.SEARCH_TYPE);
responseValidator.addValidatorModule(instanceValidatorDstu3());
responseValidator.setIgnoreValidatorExceptions(true);
return responseValidator;
}

View File

@ -1,9 +1,12 @@
package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.any;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
@ -32,6 +35,7 @@ import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.Mockito;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
@ -44,9 +48,11 @@ import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
@ -260,6 +266,127 @@ public class RequestValidatingInterceptorDstu3Test {
assertThat(status.toString(), containsString("X-FHIR-Request-Validation: {\"resourceType\":\"OperationOutcome"));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionNpeNoIgnore() throws Exception {
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(false);
Mockito.doThrow(NullPointerException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(encoded, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<diagnostics value=\"java.lang.NullPointerException\"/>"));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionNpeIgnore() throws Exception {
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(true);
Mockito.doThrow(NullPointerException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(encoded, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertThat(status.toString(), not(containsString("X-FHIR-Request-Validation")));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionIseNoIgnore() throws Exception {
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(false);
Mockito.doThrow(new InternalErrorException("FOO")).when(module).validateResource(Mockito.any(IValidationContext.class));
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(encoded, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<diagnostics value=\"FOO\"/>"));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionIseIgnore() throws Exception {
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(true);
Mockito.doThrow(InternalErrorException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
String encoded = ourCtx.newXmlParser().encodeResourceToString(patient);
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(encoded, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertThat(status.toString(), not(containsString("X-FHIR-Request-Validation")));
}
@Test
public void testCreateXmlValidNoValidatorsSpecified() throws Exception {
Patient patient = new Patient();

View File

@ -6,6 +6,7 @@ import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@ -15,6 +16,9 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -32,6 +36,7 @@ import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.Delete;
@ -41,9 +46,11 @@ import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.util.PortUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
@ -74,6 +81,127 @@ public class ResponseValidatingInterceptorDstu3Test {
ourServlet.registerInterceptor(myInterceptor);
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionNpeNoIgnore() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
patient.setGender(AdministrativeGender.MALE);
myReturnResource = patient;
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(false);
Mockito.doThrow(NullPointerException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient?foo=bar");
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<diagnostics value=\"java.lang.NullPointerException\"/>"));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionNpeIgnore() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
patient.setGender(AdministrativeGender.MALE);
myReturnResource = patient;
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(true);
Mockito.doThrow(NullPointerException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient?foo=bar");
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.toString(), not(containsString("X-FHIR-Response-Validation")));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionIseNoIgnore() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
patient.setGender(AdministrativeGender.MALE);
myReturnResource = patient;
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(false);
Mockito.doThrow(new InternalErrorException("FOO")).when(module).validateResource(Mockito.any(IValidationContext.class));
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient?foo=bar");
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(500, status.getStatusLine().getStatusCode());
assertThat(responseContent, containsString("<diagnostics value=\"FOO\"/>"));
}
@SuppressWarnings("unchecked")
@Test
public void testInterceptorExceptionIseIgnore() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("002");
patient.setGender(AdministrativeGender.MALE);
myReturnResource = patient;
myInterceptor.setAddResponseHeaderOnSeverity(null);
myInterceptor.setFailOnSeverity(null);
myInterceptor.setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum.INFORMATION);
IValidatorModule module = mock(IValidatorModule.class);
myInterceptor.addValidatorModule(module);
myInterceptor.setIgnoreValidatorExceptions(true);
Mockito.doThrow(InternalErrorException.class).when(module).validateResource(Mockito.any(IValidationContext.class));
HttpGet httpPost = new HttpGet("http://localhost:" + ourPort + "/Patient?foo=bar");
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
IOUtils.closeQuietly(status.getEntity().getContent());
ourLog.info("Response was:\n{}", status);
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
assertThat(status.toString(), not(containsString("X-FHIR-Response-Validation")));
}
/**
* Test for #345
*/
@ -89,7 +217,7 @@ public class ResponseValidatingInterceptorDstu3Test {
ourLog.info("Response was:\n{}", status);
assertEquals(204, status.getStatusLine().getStatusCode());
assertThat(status.toString(), not(containsString("X-FHIR-Request-Validation")));
assertThat(status.toString(), not(containsString("X-FHIR-Response-Validation")));
} finally {
IOUtils.closeQuietly(status);
}

View File

@ -84,6 +84,12 @@
new FluentPath search parameter definitions
for DSTU3 resources.
</action>
<action type="add">
RequestValidatingInterceptor and ResponseValidatingInterceptor
both have new method <![CDATA[<code>setIgnoreValidatorExceptions</code>]]>
which causes validator exceptions to be ignored, rather than causing
processing to be aborted.
</action>
</release>
<release version="1.5" date="2016-04-20">
<action type="fix" issue="339">