
ca.uhn.fhir.rest.server.interceptor.BaseValidatingInterceptor Maven / Gradle / Ivy
package ca.uhn.fhir.rest.server.interceptor;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
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;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
/**
* This interceptor intercepts each incoming request and if it contains a FHIR resource, validates that resource. The
* interceptor may be configured to run any validator modules, and will then add headers to the response or fail the
* request with an {@link UnprocessableEntityException HTTP 422 Unprocessable Entity}.
*/
abstract class BaseValidatingInterceptor extends InterceptorAdapter {
/**
* Default value:
*
* ${row}:${col} ${severity} ${message} (${location})
*
*/
public static final String DEFAULT_RESPONSE_HEADER_VALUE = "${row}:${col} ${severity} ${message} (${location})";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseValidatingInterceptor.class);
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 myValidatorModules;
private void addResponseIssueHeader(RequestDetails theRequestDetails, SingleValidationMessage theNext) {
// Perform any string substitutions from the message format
StrLookup> lookup = new MyLookup(theNext);
StrSubstitutor subs = new StrSubstitutor(lookup, "${", "}", '\\');
// Log the header
String headerValue = subs.replace(myResponseIssueHeaderValue);
ourLog.trace("Adding header to response: {}", headerValue);
theRequestDetails.getResponse().addHeader(myResponseIssueHeaderName, headerValue);
}
public BaseValidatingInterceptor addValidatorModule(IValidatorModule theModule) {
Validate.notNull(theModule, "theModule must not be null");
if (getValidatorModules() == null) {
setValidatorModules(new ArrayList());
}
getValidatorModules().add(theModule);
return this;
}
abstract ValidationResult doValidate(FhirValidator theValidator, T theRequest);
/**
* Fail the request by throwing an {@link UnprocessableEntityException} as a result of a validation failure.
* Subclasses may change this behaviour by providing alternate behaviour.
*/
protected void fail(RequestDetails theRequestDetails, ValidationResult theValidationResult) {
throw new UnprocessableEntityException(theRequestDetails.getServer().getFhirContext(), theValidationResult.toOperationOutcome());
}
/**
* If the validation produces a result with at least the given severity, a header with the name
* specified by {@link #setResponseOutcomeHeaderName(String)} will be added containing a JSON encoded
* OperationOutcome resource containing the validation results.
*/
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.
*/
public int getMaximumHeaderLength() {
return myMaximumHeaderLength;
}
/**
* The name of the header specified by {@link #setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum)}
*/
public String getResponseOutcomeHeaderName() {
return myResponseOutcomeHeaderName;
}
public List getValidatorModules() {
return myValidatorModules;
}
/**
* If set to true
(default is false
) this interceptor
* will exit immediately and allow processing to continue if the validator throws
* any exceptions.
*
* This setting is mostly useful in testing situations
*
*/
public boolean isIgnoreValidatorExceptions() {
return myIgnoreValidatorExceptions;
}
abstract String provideDefaultResponseHeaderName();
/**
* Sets the minimum severity at which an issue detected by the validator will result in a header being added to the
* response. Default is {@link ResultSeverityEnum#INFORMATION}. Set to null
to disable this behaviour.
*
* @see #setResponseHeaderName(String)
* @see #setResponseHeaderValue(String)
*/
public void setAddResponseHeaderOnSeverity(ResultSeverityEnum theSeverity) {
myAddResponseIssueHeaderOnSeverity = theSeverity != null ? theSeverity.ordinal() : null;
}
/**
* If the validation produces a result with at least the given severity, a header with the name
* specified by {@link #setResponseOutcomeHeaderName(String)} will be added containing a JSON encoded
* OperationOutcome resource containing the validation results.
*/
public void setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum theAddResponseOutcomeHeaderOnSeverity) {
myAddResponseOutcomeHeaderOnSeverity = theAddResponseOutcomeHeaderOnSeverity != null ? theAddResponseOutcomeHeaderOnSeverity.ordinal() : null;
}
/**
* Sets the minimum severity at which an issue detected by the validator will fail/reject the request. Default is
* {@link ResultSeverityEnum#ERROR}. Set to null
to disable this behaviour.
*/
public void setFailOnSeverity(ResultSeverityEnum theSeverity) {
myFailOnSeverity = theSeverity != null ? theSeverity.ordinal() : null;
}
/**
* If set to true
(default is false
) this interceptor
* will exit immediately and allow processing to continue if the validator throws
* any exceptions.
*
* This setting is mostly useful in testing situations
*
*/
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.
*/
public void setMaximumHeaderLength(int theMaximumHeaderLength) {
Validate.isTrue(theMaximumHeaderLength >= 100, "theMaximumHeadeerLength must be >= 100");
myMaximumHeaderLength = theMaximumHeaderLength;
}
/**
* Sets the name of the response header to add validation failures to
*
* @see #setAddResponseHeaderOnSeverity(ResultSeverityEnum)
*/
protected void setResponseHeaderName(String theResponseHeaderName) {
Validate.notBlank(theResponseHeaderName, "theResponseHeaderName must not be blank or null");
myResponseIssueHeaderName = theResponseHeaderName;
}
/**
* Sets the value to add to the response header with the name specified by {@link #setResponseHeaderName(String)}
* when validation produces a message of severity equal to or greater than
* {@link #setAddResponseHeaderOnSeverity(ResultSeverityEnum)}
*
* This field allows the following substitutions:
*
*
*
* Name
* Value
*
*
* ${line}
* The line in the request
*
*
* ${col}
* The column in the request
*
*
* ${location}
* The location in the payload as a string (typically this will be a path)
*
*
* ${severity}
* The severity of the issue
*
*
* ${message}
* The validation message
*
*
*
* @see #DEFAULT_RESPONSE_HEADER_VALUE
* @see #setAddResponseHeaderOnSeverity(ResultSeverityEnum)
*/
public void setResponseHeaderValue(String theResponseHeaderValue) {
Validate.notBlank(theResponseHeaderValue, "theResponseHeaderValue must not be blank or null");
myResponseIssueHeaderValue = theResponseHeaderValue;
}
/**
* Sets the header value to add when no issues are found at or exceeding the
* threshold specified by {@link #setAddResponseHeaderOnSeverity(ResultSeverityEnum)}
*/
public void setResponseHeaderValueNoIssues(String theResponseHeaderValueNoIssues) {
myResponseIssueHeaderValueNoIssues = theResponseHeaderValueNoIssues;
}
/**
* The name of the header specified by {@link #setAddResponseOutcomeHeaderOnSeverity(ResultSeverityEnum)}
*/
public void setResponseOutcomeHeaderName(String theResponseOutcomeHeaderName) {
Validate.notEmpty(theResponseOutcomeHeaderName, "theResponseOutcomeHeaderName can not be empty or null");
myResponseOutcomeHeaderName = theResponseOutcomeHeaderName;
}
public void setValidatorModules(List theValidatorModules) {
myValidatorModules = theValidatorModules;
}
protected void validate(T theRequest, RequestDetails theRequestDetails) {
FhirValidator validator = theRequestDetails.getServer().getFhirContext().newValidator();
if (myValidatorModules != null) {
for (IValidatorModule next : myValidatorModules) {
validator.registerValidatorModule(next);
}
}
if (theRequest == null) {
return;
}
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;
for (SingleValidationMessage next : validationResult.getMessages()) {
if (next.getSeverity().ordinal() >= myAddResponseIssueHeaderOnSeverity) {
addResponseIssueHeader(theRequestDetails, next);
found = true;
}
}
if (!found) {
if (isNotBlank(myResponseIssueHeaderValueNoIssues)) {
theRequestDetails.getResponse().addHeader(myResponseIssueHeaderName, myResponseIssueHeaderValueNoIssues);
}
}
}
if (myFailOnSeverity != null) {
for (SingleValidationMessage next : validationResult.getMessages()) {
if (next.getSeverity().ordinal() >= myFailOnSeverity) {
fail(theRequestDetails, validationResult);
return;
}
}
}
if (myAddResponseOutcomeHeaderOnSeverity != null) {
IBaseOperationOutcome outcome = null;
for (SingleValidationMessage next : validationResult.getMessages()) {
if (next.getSeverity().ordinal() >= myAddResponseOutcomeHeaderOnSeverity) {
outcome = validationResult.toOperationOutcome();
break;
}
}
if (outcome == null && myAddResponseOutcomeHeaderOnSeverity != null && myAddResponseOutcomeHeaderOnSeverity == ResultSeverityEnum.INFORMATION.ordinal()) {
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
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);
if (encoded.length() > getMaximumHeaderLength()) {
encoded = encoded.substring(0, getMaximumHeaderLength() - 3) + "...";
}
theRequestDetails.getResponse().addHeader(myResponseOutcomeHeaderName, encoded);
}
}
}
private static class MyLookup extends StrLookup {
private SingleValidationMessage myMessage;
public MyLookup(SingleValidationMessage theMessage) {
myMessage = theMessage;
}
@Override
public String lookup(String theKey) {
if ("line".equals(theKey)) {
return toString(myMessage.getLocationLine());
}
if ("col".equals(theKey)) {
return toString(myMessage.getLocationCol());
}
if ("message".equals(theKey)) {
return toString(myMessage.getMessage());
}
if ("location".equals(theKey)) {
return toString(myMessage.getLocationString());
}
if ("severity".equals(theKey)) {
return myMessage.getSeverity() != null ? myMessage.getSeverity().name() : null;
}
return "";
}
private static String toString(Object theInt) {
return theInt != null ? theInt.toString() : "";
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy