001/* 002 * #%L 003 * HAPI FHIR - Server Framework 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.rest.server.interceptor; 021 022import ca.uhn.fhir.interceptor.api.Hook; 023import ca.uhn.fhir.interceptor.api.Pointcut; 024import ca.uhn.fhir.model.api.TagList; 025import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; 026import ca.uhn.fhir.rest.annotation.Read; 027import ca.uhn.fhir.rest.annotation.Search; 028import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 029import ca.uhn.fhir.rest.api.server.RequestDetails; 030import ca.uhn.fhir.rest.api.server.ResponseDetails; 031import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; 032import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; 033import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; 034import jakarta.servlet.ServletException; 035import jakarta.servlet.http.HttpServletRequest; 036import jakarta.servlet.http.HttpServletResponse; 037import org.hl7.fhir.instance.model.api.IBaseResource; 038 039import java.io.IOException; 040 041/** 042 * Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use 043 * {@link InterceptorAdapter} in order to not need to implement every method. 044 * <p> 045 * <b>See:</b> See the <a href="https://hapifhir.io/hapi-fhir/docs/interceptors/">server 046 * interceptor documentation</a> for more information on how to use this class. 047 * </p> 048 * Note that unless otherwise stated, it is possible to throw any subclass of 049 * {@link BaseServerResponseException} from any interceptor method. 050 */ 051public interface IServerInterceptor { 052 053 /** 054 * This method is called upon any exception being thrown within the server's request processing code. This includes 055 * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as 056 * any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s 057 * thrown. 058 * <p> 059 * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In 060 * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome 061 * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they 062 * should return <code>false</code>, to indicate that they have handled the request and processing should stop. 063 * </p> 064 * 065 * @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the 066 * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been 067 * pulled out of the {@link jakarta.servlet.http.HttpServletRequest servlet request}. Note that the bean 068 * properties are not all guaranteed to be populated, depending on how early during processing the 069 * exception occurred. 070 * @param theServletRequest The incoming request 071 * @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling 072 * {@link jakarta.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return 073 * <code>false</code> to indicate that the server itself should not also provide a response. 074 * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. 075 * If your interceptor is providing a response rather than letting HAPI handle the response normally, you 076 * must return <code>false</code>. In this case, no further processing will occur and no further interceptors 077 * will be called. 078 * @throws ServletException If this exception is thrown, it will be re-thrown up to the container for handling. 079 * @throws IOException If this exception is thrown, it will be re-thrown up to the container for handling. 080 */ 081 @Hook(Pointcut.SERVER_HANDLE_EXCEPTION) 082 boolean handleException( 083 RequestDetails theRequestDetails, 084 BaseServerResponseException theException, 085 HttpServletRequest theServletRequest, 086 HttpServletResponse theServletResponse) 087 throws ServletException, IOException; 088 089 /** 090 * This method is called just before the actual implementing server method is invoked. 091 * 092 * @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the 093 * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been 094 * pulled out of the {@link HttpServletRequest servlet request}. 095 * @param theRequest The incoming request 096 * @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling 097 * {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code> 098 * to indicate that the server itself should not also provide a response. 099 * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. 100 * If your interceptor is providing a response rather than letting HAPI handle the response normally, you 101 * must return <code>false</code>. In this case, no further processing will occur and no further interceptors 102 * will be called. 103 * @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access 104 * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. 105 */ 106 @Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED) 107 boolean incomingRequestPostProcessed( 108 RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) 109 throws AuthenticationException; 110 111 /** 112 * Invoked before an incoming request is processed. Note that this method is called 113 * after the server has begin preparing the response to the incoming client request. 114 * As such, it is not able to supply a response to the incoming request in the way that 115 * {@link #incomingRequestPostProcessed(RequestDetails, HttpServletRequest, HttpServletResponse)} 116 * are. 117 * <p> 118 * This method may however throw a subclass of {@link BaseServerResponseException}, and processing 119 * will be aborted with an appropriate error returned to the client. 120 * </p> 121 * 122 * @param theOperation The type of operation that the FHIR server has determined that the client is trying to invoke 123 * @param theProcessedRequest An object which will be populated with the details which were extracted from the raw request by the 124 * server, e.g. the FHIR operation type and the parsed resource body (if any). 125 */ 126 @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED) 127 void incomingRequestPreHandled(RestOperationTypeEnum theOperation, RequestDetails theProcessedRequest); 128 129 /** 130 * This method is called before any other processing takes place for each incoming request. It may be used to provide 131 * alternate handling for some requests, or to screen requests before they are handled, etc. 132 * <p> 133 * Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server) 134 * </p> 135 * 136 * @param theRequest The incoming request 137 * @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling 138 * {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code> 139 * to indicate that the server itself should not also provide a response. 140 * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. 141 * If your interceptor is providing a response rather than letting HAPI handle the response normally, you 142 * must return <code>false</code>. In this case, no further processing will occur and no further interceptors 143 * will be called. 144 */ 145 @Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_PROCESSED) 146 boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse); 147 148 /** 149 * Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead 150 * 151 * @deprecated As of HAPI FHIR 3.2.0, this method is deprecated and will be removed in a future version of HAPI FHIR. 152 */ 153 @Deprecated 154 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 155 boolean outgoingResponse(RequestDetails theRequestDetails); 156 157 /** 158 * Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead 159 * 160 * @deprecated As of HAPI FHIR 3.2.0, this method is deprecated and will be removed in a future version of HAPI FHIR. 161 */ 162 @Deprecated 163 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 164 boolean outgoingResponse( 165 RequestDetails theRequestDetails, 166 HttpServletRequest theServletRequest, 167 HttpServletResponse theServletResponse) 168 throws AuthenticationException; 169 170 /** 171 * Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead 172 * 173 * @deprecated As of HAPI FHIR 3.2.0, this method is deprecated and will be removed in a future version of HAPI FHIR. 174 */ 175 @Deprecated 176 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 177 boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject); 178 179 /** 180 * This method is called after the server implementation method has been called, but before any attempt to stream the 181 * response back to the client. 182 * 183 * @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the 184 * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been 185 * pulled out of the {@link HttpServletRequest servlet request}. 186 * @param theResponseObject The actual object which is being streamed to the client as a response. This may be 187 * <code>null</code> if the response does not include a resource. 188 * @param theServletRequest The incoming request 189 * @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling 190 * {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code> 191 * to indicate that the server itself should not also provide a response. 192 * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. 193 * If your interceptor is providing a response rather than letting HAPI handle the response normally, you 194 * must return <code>false</code>. In this case, no further processing will occur and no further interceptors 195 * will be called. 196 * @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access 197 * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. 198 * @deprecated As of HAPI FHIR 3.3.0, this method has been deprecated in 199 * favour of {@link #outgoingResponse(RequestDetails, ResponseDetails, HttpServletRequest, HttpServletResponse)} 200 * and will be removed in a future version of HAPI FHIR. 201 */ 202 @Deprecated 203 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 204 boolean outgoingResponse( 205 RequestDetails theRequestDetails, 206 IBaseResource theResponseObject, 207 HttpServletRequest theServletRequest, 208 HttpServletResponse theServletResponse) 209 throws AuthenticationException; 210 211 /** 212 * This method is called after the server implementation method has been called, but before any attempt to stream the 213 * response back to the client. 214 * 215 * @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the 216 * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been 217 * pulled out of the {@link HttpServletRequest servlet request}. 218 * @param theResponseDetails This object contains details about the response, including 219 * the actual payload that will be returned 220 * @param theServletRequest The incoming request 221 * @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling 222 * {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code> 223 * to indicate that the server itself should not also provide a response. 224 * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do. 225 * If your interceptor is providing a response rather than letting HAPI handle the response normally, you 226 * must return <code>false</code>. In this case, no further processing will occur and no further interceptors 227 * will be called. 228 * @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access 229 * attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client. 230 */ 231 @Hook(Pointcut.SERVER_OUTGOING_RESPONSE) 232 boolean outgoingResponse( 233 RequestDetails theRequestDetails, 234 ResponseDetails theResponseDetails, 235 HttpServletRequest theServletRequest, 236 HttpServletResponse theServletResponse) 237 throws AuthenticationException; 238 239 /** 240 * Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead 241 * 242 * @deprecated As of HAPI FHIR 3.2.0, this method is deprecated and will be removed in a future version of HAPI FHIR. 243 */ 244 @Deprecated 245 boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject); 246 247 /** 248 * Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead 249 * 250 * @deprecated As of HAPI FHIR 3.2.0, this method is deprecated and will be removed in a future version of HAPI FHIR. 251 */ 252 @Deprecated 253 boolean outgoingResponse( 254 RequestDetails theRequestDetails, 255 TagList theResponseObject, 256 HttpServletRequest theServletRequest, 257 HttpServletResponse theServletResponse) 258 throws AuthenticationException; 259 260 /** 261 * This method is called upon any exception being thrown within the server's request processing code. This includes 262 * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as 263 * any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them 264 * returns a non-<code>null</code> response or the end of the list is reached), after which 265 * {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is 266 * called for each interceptor. 267 * <p> 268 * This may be used to add an OperationOutcome to a response, or to convert between exception types for any reason. 269 * </p> 270 * <p> 271 * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In 272 * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome 273 * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they 274 * should return a non-<code>null</code>, to indicate that they have handled the request and processing should stop. 275 * </p> 276 * 277 * @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to 278 * modify the exception. For example, if this interceptor has nothing to do with exception processing, it 279 * should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it 280 * should return an exception. 281 */ 282 @Hook(Pointcut.SERVER_PRE_PROCESS_OUTGOING_EXCEPTION) 283 BaseServerResponseException preProcessOutgoingException( 284 RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) 285 throws ServletException; 286 287 /** 288 * This method is called after all processing is completed for a request, but only if the 289 * request completes normally (i.e. no exception is thrown). 290 * <p> 291 * This method should not throw any exceptions. Any exception that is thrown by this 292 * method will be logged, but otherwise not acted upon. 293 * </p> 294 * <p> 295 * Note that this individual interceptors will have this method called in the reverse order from the order in 296 * which the interceptors were registered with the server. 297 * </p> 298 * 299 * @param theRequestDetails The request itself 300 */ 301 @Hook(Pointcut.SERVER_PROCESSING_COMPLETED_NORMALLY) 302 void processingCompletedNormally(ServletRequestDetails theRequestDetails); 303}