All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ca.uhn.fhir.rest.server.interceptor.BaseValidatingInterceptor Maven / Gradle / Ivy

There is a newer version: 7.6.1
Show newest version
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: *

* * * * * * * * * * * * * * * * * * * * * * * * * *
NameValue
${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