001package ca.uhn.fhir.rest.server.interceptor;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.io.IOException;
024
025import javax.servlet.ServletException;
026import javax.servlet.http.HttpServletRequest;
027import javax.servlet.http.HttpServletResponse;
028
029import org.hl7.fhir.instance.model.api.IBaseResource;
030import org.hl7.fhir.instance.model.api.IIdType;
031
032import ca.uhn.fhir.context.FhirContext;
033import ca.uhn.fhir.model.api.Bundle;
034import ca.uhn.fhir.model.api.TagList;
035import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
036import ca.uhn.fhir.rest.annotation.Read;
037import ca.uhn.fhir.rest.annotation.ResourceParam;
038import ca.uhn.fhir.rest.annotation.Search;
039import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
040import ca.uhn.fhir.rest.method.RequestDetails;
041import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
042import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
043
044/**
045 * Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use
046 * {@link InterceptorAdapter} in order to not need to implement every method.
047 * <p>
048 * <b>See:</b> See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_interceptor.html">server
049 * interceptor documentation</a> for more information on how to use this class.
050 * </p>
051 */
052public interface IServerInterceptor {
053
054        /**
055         * This method is called upon any exception being thrown within the server's request processing code. This includes
056         * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
057         * any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s
058         * thrown.
059         * <p>
060         * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In
061         * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
062         * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
063         * should return <code>false</code>, to indicate that they have handled the request and processing should stop.
064         * </p>
065         * 
066         *
067         * @param theRequestDetails
068         *           A bean containing details about the request that is about to be processed, including details such as the
069         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
070         *           pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
071         *           properties are not all guaranteed to be populated, depending on how early during processing the
072         *           exception occurred.
073         * @param theServletRequest
074         *           The incoming request
075         * @param theServletResponse
076         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
077         *           {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
078         *           <code>false</code> to indicate that the server itself should not also provide a response.
079         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
080         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
081         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
082         *         will be called.
083         * @throws ServletException
084         *            If this exception is thrown, it will be re-thrown up to the container for handling.
085         * @throws IOException
086         *            If this exception is thrown, it will be re-thrown up to the container for handling.
087         */
088        boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws ServletException, IOException;
089
090        /**
091         * This method is called just before the actual implementing server method is invoked.
092         * <p>
093         * Note about exceptions:
094         * </p>
095         * 
096         * @param theRequestDetails
097         *           A bean containing details about the request that is about to be processed, including details such as the
098         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
099         *           pulled out of the {@link HttpServletRequest servlet request}.
100         * @param theRequest
101         *           The incoming request
102         * @param theResponse
103         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
104         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
105         *           to indicate that the server itself should not also provide a response.
106         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
107         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
108         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
109         *         will be called.
110         * @throws AuthenticationException
111         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
112         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
113         */
114        boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
115
116        /**
117         * Invoked before an incoming request is processed
118         * 
119         * @param theServletRequest
120         *           The incoming servlet request as provided by the servlet container
121         * @param theOperation
122         *           The type of operation that the FHIR server has determined that the client is trying to invoke
123         * @param theProcessedRequest
124         *           An object which will be populated with the details which were extracted from the raw request by the
125         *           server, e.g. the FHIR operation type and the parsed resource body (if any).
126         */
127        void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails 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
137         *           The incoming request
138         * @param theResponse
139         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
140         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
141         *           to indicate that the server itself should not also provide a response.
142         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
143         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
144         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
145         *         will be called.
146         */
147        boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
148
149        /**
150         * This method is called after the server implementation method has been called, but before any attempt to stream the
151         * response back to the client
152         * 
153         * @param theRequestDetails
154         *           A bean containing details about the request that is about to be processed, including
155         * @param theResponseObject
156         *           The actual object which is being streamed to the client as a response
157         * @param theServletRequest
158         *           The incoming request
159         * @param theServletResponse
160         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
161         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
162         *           to indicate that the server itself should not also provide a response.
163         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
164         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
165         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
166         *         will be called.
167         * @throws AuthenticationException
168         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
169         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
170         */
171        boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
172
173        /**
174         * This method is called after the server implementation method has been called, but before any attempt to stream the
175         * response back to the client
176         * 
177         * @param theRequestDetails
178         *           A bean containing details about the request that is about to be processed, including
179         * @param theResponseObject
180         *           The actual object which is being streamed to the client as a response
181         * @param theServletRequest
182         *           The incoming request
183         * @param theServletResponse
184         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
185         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
186         *           to indicate that the server itself should not also provide a response.
187         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
188         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
189         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
190         *         will be called.
191         * @throws AuthenticationException
192         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
193         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
194         */
195        boolean outgoingResponse(RequestDetails theRequest, Bundle bundle);
196
197        /**
198         * This method is called after the server implementation method has been called, but before any attempt to stream the
199         * response back to the client
200         * 
201         * @param theRequestDetails
202         *           A bean containing details about the request that is about to be processed, including details such as the
203         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
204         *           pulled out of the {@link HttpServletRequest servlet request}.
205         * @param theServletRequest
206         *           The incoming request
207         * @param theServletResponse
208         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
209         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
210         *           to indicate that the server itself should not also provide a response.
211         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
212         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
213         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
214         *         will be called.
215         * @throws AuthenticationException
216         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
217         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
218         */
219        boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
220
221        /**
222         * This method is called after the server implementation method has been called, but before any attempt to stream the
223         * response back to the client
224         * 
225         * @param theRequestDetails
226         *           A bean containing details about the request that is about to be processed, including details such as the
227         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
228         *           pulled out of the {@link HttpServletRequest servlet request}.
229         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
230         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
231         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
232         *         will be called.
233         * @throws AuthenticationException
234         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
235         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
236         */
237        boolean outgoingResponse(RequestDetails theRequestDetails);
238
239        /**
240         * This method is called after the server implementation method has been called, but before any attempt to stream the
241         * response back to the client
242         * 
243         * @param theRequestDetails
244         *           A bean containing details about the request that is about to be processed, including details such as the
245         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
246         *           pulled out of the {@link HttpServletRequest servlet request}.
247         * @param theResponseObject
248         *           The actual object which is being streamed to the client as a response
249         * @param theServletRequest
250         *           The incoming request
251         * @param theServletResponse
252         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
253         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
254         *           to indicate that the server itself should not also provide a response.
255         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
256         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
257         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
258         *         will be called.
259         * @throws AuthenticationException
260         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
261         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
262         */
263        boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
264
265        /**
266         * This method is called after the server implementation method has been called, but before any attempt to stream the
267         * response back to the client
268         * 
269         * @param theRequestDetails
270         *           A bean containing details about the request that is about to be processed, including details such as the
271         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
272         *           pulled out of the {@link HttpServletRequest servlet request}.
273         * @param theResponseObject
274         *           The actual object which is being streamed to the client as a response
275         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
276         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
277         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
278         *         will be called.
279         * @throws AuthenticationException
280         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
281         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
282         */
283        boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject);
284
285        /**
286         * This method is called after the server implementation method has been called, but before any attempt to stream the
287         * response back to the client
288         * 
289         * @param theRequestDetails
290         *           A bean containing details about the request that is about to be processed, including details such as the
291         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
292         *           pulled out of the {@link HttpServletRequest servlet request}.
293         * @param theResponseObject
294         *           The actual object which is being streamed to the client as a response
295         * @param theServletRequest
296         *           The incoming request
297         * @param theServletResponse
298         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
299         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
300         *           to indicate that the server itself should not also provide a response.
301         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
302         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
303         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
304         *         will be called.
305         * @throws AuthenticationException
306         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
307         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
308         */
309        boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
310
311        /**
312         * This method is called after the server implementation method has been called, but before any attempt to stream the
313         * response back to the client
314         * 
315         * @param theRequestDetails
316         *           A bean containing details about the request that is about to be processed, including details such as the
317         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
318         *           pulled out of the {@link HttpServletRequest servlet request}.
319         * @param theResponseObject
320         *           The actual object which is being streamed to the client as a response
321         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
322         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
323         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
324         *         will be called.
325         * @throws AuthenticationException
326         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
327         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
328         */
329        boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject);
330
331        /**
332         * This method is called upon any exception being thrown within the server's request processing code. This includes
333         * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
334         * any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them
335         * returns a non-<code>null</code> response or the end of the list is reached), after which
336         * {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is
337         * called for each interceptor.
338         * <p>
339         * This may be used to add an OperationOutcome to a response, or to convert between exception types for any reason.
340         * </p>
341         * <p>
342         * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In
343         * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
344         * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
345         * should return a non-<code>null</code>, to indicate that they have handled the request and processing should stop.
346         * </p>
347         * 
348         * @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to
349         *         modify the exception. For example, if this interceptor has nothing to do with exception processing, it
350         *         should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it
351         *         should return an exception.
352         */
353        BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException;
354
355        public static class ActionRequestDetails {
356                private final IIdType myId;
357                private boolean myLocked = false;
358                private IBaseResource myResource;
359                private final String myResourceType;
360                private final FhirContext myContext;
361
362                public ActionRequestDetails(IIdType theId, String theResourceType, FhirContext theContext) {
363                        myId = theId;
364                        myResourceType = theResourceType;
365                        myContext = theContext;
366                }
367
368                public ActionRequestDetails(RequestDetails theRequestDetails) {
369                        myId = theRequestDetails.getId();
370                        myResourceType = theRequestDetails.getResourceName();
371                        myContext = theRequestDetails.getServer().getFhirContext();
372                }
373
374                public FhirContext getContext() {
375                        return myContext;
376                }
377
378                public ActionRequestDetails(IIdType theId, String theResourceType, IBaseResource theResource, FhirContext theContext) {
379                        this(theId, theResourceType, theContext);
380                        myResource = theResource;
381                }
382
383                /**
384                 * Returns the ID of the incoming request (typically this is from the request URL)
385                 */
386                public IIdType getId() {
387                        return myId;
388                }
389
390                /**
391                 * For requests where a resource is passed from the client to the server (e.g. create, update, etc.) this method
392                 * will return the resource which was provided by the client. Otherwise, this method will return <code>null</code>
393                 * .
394                 * <p>
395                 * Note that this method is currently only populated if the handling method has a parameter annotated with the
396                 * {@link ResourceParam} annotation.
397                 * </p>
398                 */
399                public IBaseResource getResource() {
400                        return myResource;
401                }
402
403                /**
404                 * Returns the resource type this request pertains to, or <code>null</code> if this request is not type specific
405                 * (e.g. server-history)
406                 */
407                public String getResourceType() {
408                        return myResourceType;
409                }
410
411                /**
412                 * Prevent any further changes to this object
413                 */
414                public void lock() {
415                        myLocked = true;
416                }
417
418                /**
419                 * This method should not be called by client code
420                 */
421                public void setResource(IBaseResource theObject) {
422                        validateNotLocked();
423                        myResource = theObject;
424                }
425
426                private void validateNotLocked() {
427                        if (myLocked) {
428                                throw new IllegalStateException("Values on this object may not be changed");
429                        }
430                }
431        }
432
433}