Skip to content

Commit

Permalink
Merge 34cd3a9 into 95ef644
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesagnew committed Mar 15, 2017
2 parents 95ef644 + 34cd3a9 commit 40be716
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 38 deletions.
Expand Up @@ -267,7 +267,10 @@ public void setValidatorModules(List<IValidatorModule> theValidatorModules) {
*/
protected void postProcessResult(RequestDetails theRequestDetails, ValidationResult theValidationResult) { }

protected void validate(T theRequest, RequestDetails theRequestDetails) {
/**
* Note: May return null
*/
protected ValidationResult validate(T theRequest, RequestDetails theRequestDetails) {
FhirValidator validator = theRequestDetails.getServer().getFhirContext().newValidator();
if (myValidatorModules != null) {
for (IValidatorModule next : myValidatorModules) {
Expand All @@ -276,7 +279,7 @@ protected void validate(T theRequest, RequestDetails theRequestDetails) {
}

if (theRequest == null) {
return;
return null;
}

ValidationResult validationResult;
Expand All @@ -285,7 +288,7 @@ protected void validate(T theRequest, RequestDetails theRequestDetails) {
} catch (Exception e) {
if (myIgnoreValidatorExceptions) {
ourLog.warn("Validator threw an exception during validation", e);
return;
return null;
}
if (e instanceof BaseServerResponseException) {
throw (BaseServerResponseException)e;
Expand All @@ -312,7 +315,7 @@ protected void validate(T theRequest, RequestDetails theRequestDetails) {
for (SingleValidationMessage next : validationResult.getMessages()) {
if (next.getSeverity().ordinal() >= myFailOnSeverity) {
fail(theRequestDetails, validationResult);
return;
return validationResult;
}
}
}
Expand Down Expand Up @@ -342,6 +345,8 @@ protected void validate(T theRequest, RequestDetails theRequestDetails) {
}

postProcessResult(theRequestDetails, validationResult);

return validationResult;
}

private static class MyLookup extends StrLookup<String> {
Expand Down
Expand Up @@ -12,7 +12,7 @@
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
Expand All @@ -27,6 +27,9 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;

import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.EncodingEnum;
Expand All @@ -51,6 +54,19 @@ public class RequestValidatingInterceptor extends BaseValidatingInterceptor<Stri

private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RequestValidatingInterceptor.class);

/**
* A {@link RequestDetails#getUserData() user data} entry will be created with this
* key which contains the {@link ValidationResult} from validating the request.
*/
public static final String REQUEST_VALIDATION_RESULT = RequestValidatingInterceptor.class.getName() + "_REQUEST_VALIDATION_RESULT";

private boolean myAddValidationResultsToResponseOperationOutcome = true;

@Override
ValidationResult doValidate(FhirValidator theValidator, String theRequest) {
return theValidator.validateWithResult(theRequest);
}

@Override
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequestDetails);
Expand All @@ -67,34 +83,69 @@ public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, Ht
return true;
}

validate(requestText, theRequestDetails);
ValidationResult validationResult = validate(requestText, theRequestDetails);

// The JPA server will use this
theRequestDetails.getUserData().put(REQUEST_VALIDATION_RESULT, validationResult);

return true;
}


/**
* Sets the name of the response header to add validation failures to
*
* @see #DEFAULT_RESPONSE_HEADER_NAME
* @see #setAddResponseHeaderOnSeverity(ResultSeverityEnum)
* If set to {@literal true} (default is true), the validation results
* will be added to the OperationOutcome being returned to the client,
* unless the response being returned is not an OperationOutcome
* to begin with (e.g. if the client has requested
* <code>Return: prefer=representation</code>)
*/
@Override
public void setResponseHeaderName(String theResponseHeaderName) {
super.setResponseHeaderName(theResponseHeaderName);
public boolean isAddValidationResultsToResponseOperationOutcome() {
return myAddValidationResultsToResponseOperationOutcome;
}

@Override
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
if (myAddValidationResultsToResponseOperationOutcome) {
if (theResponseObject instanceof IBaseOperationOutcome) {
IBaseOperationOutcome oo = (IBaseOperationOutcome) theResponseObject;

if (theRequestDetails != null) {
ValidationResult validationResult = (ValidationResult) theRequestDetails.getUserData().get(RequestValidatingInterceptor.REQUEST_VALIDATION_RESULT);
if (validationResult != null) {
validationResult.populateOperationOutcome(oo);
}
}

}
}

return true;
}

@Override
String provideDefaultResponseHeaderName() {
return DEFAULT_RESPONSE_HEADER_NAME;
}

/**
* If set to {@literal true} (default is true), the validation results
* will be added to the OperationOutcome being returned to the client,
* unless the response being returned is not an OperationOutcome
* to begin with (e.g. if the client has requested
* <code>Return: prefer=representation</code>)
*/
public void setAddValidationResultsToResponseOperationOutcome(boolean theAddValidationResultsToResponseOperationOutcome) {
myAddValidationResultsToResponseOperationOutcome = theAddValidationResultsToResponseOperationOutcome;
}

/**
* Sets the name of the response header to add validation failures to
*
* @see #DEFAULT_RESPONSE_HEADER_NAME
* @see #setAddResponseHeaderOnSeverity(ResultSeverityEnum)
*/
@Override
ValidationResult doValidate(FhirValidator theValidator, String theRequest) {
return theValidator.validateWithResult(theRequest);
public void setResponseHeaderName(String theResponseHeaderName) {
super.setResponseHeaderName(theResponseHeaderName);
}


}
Expand Up @@ -102,6 +102,14 @@ public IBaseOperationOutcome getOperationOutcome() {
*/
public IBaseOperationOutcome toOperationOutcome() {
IBaseOperationOutcome oo = (IBaseOperationOutcome) myCtx.getResourceDefinition("OperationOutcome").newInstance();
populateOperationOutcome(oo);
return oo;
}

/**
* Populate an operation outcome with the results of the validation
*/
public void populateOperationOutcome(IBaseOperationOutcome theOperationOutcome) {
for (SingleValidationMessage next : myMessages) {
String location;
if (isNotBlank(next.getLocationString())) {
Expand All @@ -112,15 +120,13 @@ public IBaseOperationOutcome toOperationOutcome() {
location = null;
}
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
OperationOutcomeUtil.addIssue(myCtx, oo, severity, next.getMessage(), location, ExceptionHandlingInterceptor.PROCESSING);
OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, severity, next.getMessage(), location, ExceptionHandlingInterceptor.PROCESSING);
}

if (myMessages.isEmpty()) {
String message = myCtx.getLocalizer().getMessage(ValidationResult.class, "noIssuesDetected");
OperationOutcomeUtil.addIssue(myCtx, oo, "information", message, null, "informational");
OperationOutcomeUtil.addIssue(myCtx, theOperationOutcome, "information", message, null, "informational");
}

return oo;
}

@Override
Expand Down
Expand Up @@ -138,6 +138,7 @@
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;

Expand Down Expand Up @@ -402,6 +403,36 @@ public void testCreateBundle() throws IOException {
ourClient.create().resource(input).execute().getResource();
}

@Test
public void testCreateIncludesRequestValidatorInterceptorOutcome() throws IOException {
RequestValidatingInterceptor interceptor = new RequestValidatingInterceptor();
assertTrue(interceptor.isAddValidationResultsToResponseOperationOutcome());
interceptor.setFailOnSeverity(null);

ourRestServer.registerInterceptor(interceptor);
try {
// Missing status, which is mandatory
Observation obs = new Observation();
obs.addIdentifier().setSystem("urn:foo").setValue("bar");
IBaseResource outcome = ourClient.create().resource(obs).execute().getOperationOutcome();

String encodedOo = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(encodedOo);
assertThat(encodedOo, containsString("The content of element 'Observation' is not complete"));
assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/"));

interceptor.setAddValidationResultsToResponseOperationOutcome(false);
outcome = ourClient.create().resource(obs).execute().getOperationOutcome();
encodedOo = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
ourLog.info(encodedOo);
assertThat(encodedOo, not(containsString("The content of element 'Observation' is not complete")));
assertThat(encodedOo, containsString("Successfully created resource \\\"Observation/"));

} finally {
ourRestServer.unregisterInterceptor(interceptor);
}
}

@Test
public void testCreateConditional() {
Patient patient = new Patient();
Expand Down Expand Up @@ -800,7 +831,7 @@ public void testDeleteResourceConditional1() throws IOException {
}

// Delete should now have no matches

delete = new HttpDelete(ourServerBase + "/Patient?name=" + methodName);
response = ourHttpClient.execute(delete);
try {
Expand Down Expand Up @@ -3658,7 +3689,8 @@ public void testValidateResourceWithNoIdParameters() throws IOException {
assertThat(resp, not(containsString("Resource has no id")));
assertThat(resp, containsString("<pre>No issues detected during validation</pre>"));
assertThat(resp,
stringContainsInOrder("<issue>", "<severity value=\"information\"/>", "<code value=\"informational\"/>", "<diagnostics value=\"No issues detected during validation\"/>", "</issue>"));
stringContainsInOrder("<issue>", "<severity value=\"information\"/>", "<code value=\"informational\"/>", "<diagnostics value=\"No issues detected during validation\"/>",
"</issue>"));
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
Expand All @@ -3684,7 +3716,8 @@ public void testValidateResourceWithNoIdRaw() throws IOException {
assertThat(resp, not(containsString("Resource has no id")));
assertThat(resp, containsString("<pre>No issues detected during validation</pre>"));
assertThat(resp,
stringContainsInOrder("<issue>", "<severity value=\"information\"/>", "<code value=\"informational\"/>", "<diagnostics value=\"No issues detected during validation\"/>", "</issue>"));
stringContainsInOrder("<issue>", "<severity value=\"information\"/>", "<code value=\"informational\"/>", "<diagnostics value=\"No issues detected during validation\"/>",
"</issue>"));
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
Expand Down

0 comments on commit 40be716

Please sign in to comment.