001package ca.uhn.fhir.rest.server.interceptor;
002
003import static org.apache.commons.lang3.StringUtils.isBlank;
004
005/*
006 * #%L
007 * HAPI FHIR - Core Library
008 * %%
009 * Copyright (C) 2014 - 2017 University Health Network
010 * %%
011 * Licensed under the Apache License, Version 2.0 (the "License");
012 * you may not use this file except in compliance with the License.
013 * You may obtain a copy of the License at
014 * 
015 * http://www.apache.org/licenses/LICENSE-2.0
016 * 
017 * Unless required by applicable law or agreed to in writing, software
018 * distributed under the License is distributed on an "AS IS" BASIS,
019 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 * See the License for the specific language governing permissions and
021 * limitations under the License.
022 * #L%
023 */
024
025import java.nio.charset.Charset;
026
027import javax.servlet.http.HttpServletRequest;
028import javax.servlet.http.HttpServletResponse;
029
030import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
031import org.hl7.fhir.instance.model.api.IBaseResource;
032
033import ca.uhn.fhir.rest.method.RequestDetails;
034import ca.uhn.fhir.rest.param.ResourceParameter;
035import ca.uhn.fhir.rest.server.EncodingEnum;
036import ca.uhn.fhir.rest.server.RestfulServerUtils;
037import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
038import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
039import ca.uhn.fhir.validation.FhirValidator;
040import ca.uhn.fhir.validation.ResultSeverityEnum;
041import ca.uhn.fhir.validation.ValidationResult;
042
043/**
044 * This interceptor intercepts each incoming request and if it contains a FHIR resource, validates that resource. The
045 * interceptor may be configured to run any validator modules, and will then add headers to the response or fail the
046 * request with an {@link UnprocessableEntityException HTTP 422 Unprocessable Entity}.
047 */
048public class RequestValidatingInterceptor extends BaseValidatingInterceptor<String> {
049
050        /**
051         * X-HAPI-Request-Validation
052         */
053        public static final String DEFAULT_RESPONSE_HEADER_NAME = "X-FHIR-Request-Validation";
054
055        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RequestValidatingInterceptor.class);
056
057        /**
058         * A {@link RequestDetails#getUserData() user data} entry will be created with this
059         * key which contains the {@link ValidationResult} from validating the request.
060         */
061        public static final String REQUEST_VALIDATION_RESULT = RequestValidatingInterceptor.class.getName() + "_REQUEST_VALIDATION_RESULT";
062
063        private boolean myAddValidationResultsToResponseOperationOutcome = true;
064
065        @Override
066        ValidationResult doValidate(FhirValidator theValidator, String theRequest) {
067                return theValidator.validateWithResult(theRequest);
068        }
069
070        @Override
071        public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
072                EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequestDetails);
073                if (encoding == null) {
074                        ourLog.trace("Incoming request does not appear to be FHIR, not going to validate");
075                        return true;
076                }
077
078                Charset charset = ResourceParameter.determineRequestCharset(theRequestDetails);
079                String requestText = new String(theRequestDetails.loadRequestContents(), charset);
080
081                if (isBlank(requestText)) {
082                        ourLog.trace("Incoming request does not have a body");
083                        return true;
084                }
085
086                ValidationResult validationResult = validate(requestText, theRequestDetails);
087
088                // The JPA server will use this
089                theRequestDetails.getUserData().put(REQUEST_VALIDATION_RESULT, validationResult);
090
091                return true;
092        }
093
094        /**
095         * If set to {@literal true} (default is true), the validation results
096         * will be added to the OperationOutcome being returned to the client,
097         * unless the response being returned is not an OperationOutcome
098         * to begin with (e.g. if the client has requested 
099         * <code>Return: prefer=representation</code>)
100         */
101        public boolean isAddValidationResultsToResponseOperationOutcome() {
102                return myAddValidationResultsToResponseOperationOutcome;
103        }
104
105        @Override
106        public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) {
107                if (myAddValidationResultsToResponseOperationOutcome) {
108                        if (theResponseObject instanceof IBaseOperationOutcome) {
109                                IBaseOperationOutcome oo = (IBaseOperationOutcome) theResponseObject;
110
111                                if (theRequestDetails != null) {
112                                        ValidationResult validationResult = (ValidationResult) theRequestDetails.getUserData().get(RequestValidatingInterceptor.REQUEST_VALIDATION_RESULT);
113                                        if (validationResult != null) {
114                                                validationResult.populateOperationOutcome(oo);
115                                        }
116                                }
117
118                        }
119                }
120
121                return true;
122        }
123
124        @Override
125        String provideDefaultResponseHeaderName() {
126                return DEFAULT_RESPONSE_HEADER_NAME;
127        }
128
129        /**
130         * If set to {@literal true} (default is true), the validation results
131         * will be added to the OperationOutcome being returned to the client,
132         * unless the response being returned is not an OperationOutcome
133         * to begin with (e.g. if the client has requested 
134         * <code>Return: prefer=representation</code>)
135         */
136        public void setAddValidationResultsToResponseOperationOutcome(boolean theAddValidationResultsToResponseOperationOutcome) {
137                myAddValidationResultsToResponseOperationOutcome = theAddValidationResultsToResponseOperationOutcome;
138        }
139
140        /**
141         * Sets the name of the response header to add validation failures to
142         * 
143         * @see #DEFAULT_RESPONSE_HEADER_NAME
144         * @see #setAddResponseHeaderOnSeverity(ResultSeverityEnum)
145         */
146        @Override
147        public void setResponseHeaderName(String theResponseHeaderName) {
148                super.setResponseHeaderName(theResponseHeaderName);
149        }
150
151}