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}