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}