001package ca.uhn.fhir.rest.server.interceptor;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 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;
024import java.util.Collections;
025import java.util.List;
026import java.util.Map;
027
028import javax.servlet.ServletException;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031
032import org.hl7.fhir.instance.model.api.IBaseResource;
033import org.hl7.fhir.instance.model.api.IIdType;
034
035import ca.uhn.fhir.context.FhirContext;
036import ca.uhn.fhir.model.api.Bundle;
037import ca.uhn.fhir.model.api.TagList;
038import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
039import ca.uhn.fhir.rest.annotation.Read;
040import ca.uhn.fhir.rest.annotation.ResourceParam;
041import ca.uhn.fhir.rest.annotation.Search;
042import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
043import ca.uhn.fhir.rest.method.RequestDetails;
044import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
045import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
046import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
047import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
048
049/**
050 * Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use
051 * {@link InterceptorAdapter} in order to not need to implement every method.
052 * <p>
053 * <b>See:</b> See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_interceptor.html">server
054 * interceptor documentation</a> for more information on how to use this class.
055 * </p>
056 */
057public interface IServerInterceptor {
058
059        /**
060         * This method is called upon any exception being thrown within the server's request processing code. This includes
061         * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
062         * any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s
063         * thrown.
064         * <p>
065         * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In
066         * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
067         * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
068         * should return <code>false</code>, to indicate that they have handled the request and processing should stop.
069         * </p>
070         * 
071         *
072         * @param theRequestDetails
073         *           A bean containing details about the request that is about to be processed, including details such as the
074         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
075         *           pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
076         *           properties are not all guaranteed to be populated, depending on how early during processing the
077         *           exception occurred.
078         * @param theServletRequest
079         *           The incoming request
080         * @param theServletResponse
081         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
082         *           {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
083         *           <code>false</code> to indicate that the server itself should not also provide a response.
084         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
085         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
086         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
087         *         will be called.
088         * @throws ServletException
089         *            If this exception is thrown, it will be re-thrown up to the container for handling.
090         * @throws IOException
091         *            If this exception is thrown, it will be re-thrown up to the container for handling.
092         */
093        boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
094                        throws ServletException, IOException;
095
096        /**
097         * This method is called just before the actual implementing server method is invoked.
098         * <p>
099         * Note about exceptions:
100         * </p>
101         * 
102         * @param theRequestDetails
103         *           A bean containing details about the request that is about to be processed, including details such as the
104         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
105         *           pulled out of the {@link HttpServletRequest servlet request}.
106         * @param theRequest
107         *           The incoming request
108         * @param theResponse
109         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
110         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
111         *           to indicate that the server itself should not also provide a response.
112         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
113         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
114         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
115         *         will be called.
116         * @throws AuthenticationException
117         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
118         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
119         */
120        boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
121
122        /**
123         * Invoked before an incoming request is processed. Note that this method is called
124         * after the server has begin preparing the response to the incoming client request.
125         * As such, it is not able to supply a response to the incoming request in the way that
126         * {@link #incomingRequestPreHandled(RestOperationTypeEnum, ActionRequestDetails)} and
127         * {@link #incomingRequestPostProcessed(RequestDetails, HttpServletRequest, HttpServletResponse)}
128         * are.
129         * <p>
130         * This method may however throw a subclass of {@link BaseServerResponseException}, and processing
131         * will be aborted with an appropriate error returned to the client.
132         * </p>
133         * 
134         * @param theServletRequest
135         *           The incoming servlet request as provided by the servlet container
136         * @param theOperation
137         *           The type of operation that the FHIR server has determined that the client is trying to invoke
138         * @param theProcessedRequest
139         *           An object which will be populated with the details which were extracted from the raw request by the
140         *           server, e.g. the FHIR operation type and the parsed resource body (if any).
141         */
142        void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest);
143
144        /**
145         * This method is called before any other processing takes place for each incoming request. It may be used to provide
146         * alternate handling for some requests, or to screen requests before they are handled, etc.
147         * <p>
148         * Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
149         * </p>
150         * 
151         * @param theRequest
152         *           The incoming request
153         * @param theResponse
154         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
155         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
156         *           to indicate that the server itself should not also provide a response.
157         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
158         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
159         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
160         *         will be called.
161         */
162        boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
163
164        /**
165         * This method is called after the server implementation method has been called, but before any attempt to stream the
166         * response back to the client
167         * 
168         * @param theRequestDetails
169         *           A bean containing details about the request that is about to be processed, including details such as the
170         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
171         *           pulled out of the {@link HttpServletRequest servlet request}.
172         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
173         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
174         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
175         *         will be called.
176         * @throws AuthenticationException
177         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
178         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
179         */
180        boolean outgoingResponse(RequestDetails theRequestDetails);
181
182        /**
183         * This method is called after the server implementation method has been called, but before any attempt to stream the
184         * response back to the client
185         * 
186         * @param theRequestDetails
187         *           A bean containing details about the request that is about to be processed, including
188         * @param theResponseObject
189         *           The actual object which is being streamed to the client as a response
190         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
191         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
192         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
193         *         will be called.
194         * @throws AuthenticationException
195         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
196         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
197         */
198        boolean outgoingResponse(RequestDetails theRequest, Bundle theResponseObject);
199
200        /**
201         * This method is called after the server implementation method has been called, but before any attempt to stream the
202         * response back to the client
203         * 
204         * @param theRequestDetails
205         *           A bean containing details about the request that is about to be processed, including
206         * @param theResponseObject
207         *           The actual object which is being streamed to the client as a response
208         * @param theServletRequest
209         *           The incoming request
210         * @param theServletResponse
211         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
212         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
213         *           to indicate that the server itself should not also provide a response.
214         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
215         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
216         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
217         *         will be called.
218         * @throws AuthenticationException
219         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
220         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
221         */
222        boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
223
224        /**
225         * This method is called after the server implementation method has been called, but before any attempt to stream the
226         * response back to the client
227         * 
228         * @param theRequestDetails
229         *           A bean containing details about the request that is about to be processed, including details such as the
230         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
231         *           pulled out of the {@link HttpServletRequest servlet request}.
232         * @param theServletRequest
233         *           The incoming request
234         * @param theServletResponse
235         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
236         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
237         *           to indicate that the server itself should not also provide a response.
238         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
239         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
240         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
241         *         will be called.
242         * @throws AuthenticationException
243         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
244         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
245         */
246        boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
247
248        /**
249         * This method is called after the server implementation method has been called, but before any attempt to stream the
250         * response back to the client
251         * 
252         * @param theRequestDetails
253         *           A bean containing details about the request that is about to be processed, including details such as the
254         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
255         *           pulled out of the {@link HttpServletRequest servlet request}.
256         * @param theResponseObject
257         *           The actual object which is being streamed to the client as a response
258         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
259         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
260         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
261         *         will be called.
262         * @throws AuthenticationException
263         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
264         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
265         */
266        boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject);
267
268        /**
269         * This method is called after the server implementation method has been called, but before any attempt to stream the
270         * response back to the client
271         * 
272         * @param theRequestDetails
273         *           A bean containing details about the request that is about to be processed, including details such as the
274         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
275         *           pulled out of the {@link HttpServletRequest servlet request}.
276         * @param theResponseObject
277         *           The actual object which is being streamed to the client as a response
278         * @param theServletRequest
279         *           The incoming request
280         * @param theServletResponse
281         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
282         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
283         *           to indicate that the server itself should not also provide a response.
284         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
285         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
286         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
287         *         will be called.
288         * @throws AuthenticationException
289         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
290         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
291         */
292        boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
293                        throws AuthenticationException;
294
295        /**
296         * This method is called after the server implementation method has been called, but before any attempt to stream the
297         * response back to the client
298         * 
299         * @param theRequestDetails
300         *           A bean containing details about the request that is about to be processed, including details such as the
301         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
302         *           pulled out of the {@link HttpServletRequest servlet request}.
303         * @param theResponseObject
304         *           The actual object which is being streamed to the client as a response
305         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
306         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
307         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
308         *         will be called.
309         * @throws AuthenticationException
310         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
311         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
312         */
313        boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject);
314
315        /**
316         * This method is called after the server implementation method has been called, but before any attempt to stream the
317         * response back to the client
318         * 
319         * @param theRequestDetails
320         *           A bean containing details about the request that is about to be processed, including details such as the
321         *           resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
322         *           pulled out of the {@link HttpServletRequest servlet request}.
323         * @param theResponseObject
324         *           The actual object which is being streamed to the client as a response
325         * @param theServletRequest
326         *           The incoming request
327         * @param theServletResponse
328         *           The response. Note that interceptors may choose to provide a response (i.e. by calling
329         *           {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
330         *           to indicate that the server itself should not also provide a response.
331         * @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
332         *         If your interceptor is providing a response rather than letting HAPI handle the response normally, you
333         *         must return <code>false</code>. In this case, no further processing will occur and no further interceptors
334         *         will be called.
335         * @throws AuthenticationException
336         *            This exception may be thrown to indicate that the interceptor has detected an unauthorized access
337         *            attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
338         */
339        boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
340
341        /**
342         * This method is called upon any exception being thrown within the server's request processing code. This includes
343         * any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
344         * any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them
345         * returns a non-<code>null</code> response or the end of the list is reached), after which
346         * {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is
347         * called for each interceptor.
348         * <p>
349         * This may be used to add an OperationOutcome to a response, or to convert between exception types for any reason.
350         * </p>
351         * <p>
352         * Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In
353         * this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
354         * OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
355         * should return a non-<code>null</code>, to indicate that they have handled the request and processing should stop.
356         * </p>
357         * 
358         * @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to
359         *         modify the exception. For example, if this interceptor has nothing to do with exception processing, it
360         *         should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it
361         *         should return an exception.
362         */
363        BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException;
364
365        /**
366         * This method is called after all processing is completed for a request, but only if the
367         * request completes normally (i.e. no exception is thrown).
368         * <p>
369         * Note that this individual interceptors will have this method called in the reverse order from the order in 
370         * which the interceptors were registered with the server. 
371         * </p>
372         * @param theRequestDetails
373         *           The request itself
374         */
375        void processingCompletedNormally(ServletRequestDetails theRequestDetails);
376
377        public static class ActionRequestDetails {
378                private final FhirContext myContext;
379                private final IIdType myId;
380                private RequestDetails myRequestDetails;
381                private IBaseResource myResource;
382                private final String myResourceType;
383
384                public ActionRequestDetails(RequestDetails theRequestDetails) {
385                        myId = theRequestDetails.getId();
386                        myResourceType = theRequestDetails.getResourceName();
387                        myContext = theRequestDetails.getServer().getFhirContext();
388                        myRequestDetails = theRequestDetails;
389                }
390
391                public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, IBaseResource theResource) {
392                        this(theRequestDetails, theContext, theContext.getResourceDefinition(theResource).getName(), theResource.getIdElement());
393                        myResource = theResource;
394                }
395
396                public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, String theResourceType, IIdType theId) {
397                        myId = theId;
398                        myResourceType = theResourceType;
399                        myContext = theContext;
400                        myRequestDetails = theRequestDetails;
401                }
402
403                public ActionRequestDetails(RequestDetails theRequestDetails, IBaseResource theResource) {
404                        this(theRequestDetails, theRequestDetails.getServer().getFhirContext().getResourceDefinition(theResource).getName(), theResource.getIdElement());
405                        myResource = theResource;
406                }
407
408                public ActionRequestDetails(RequestDetails theRequestDetails, IBaseResource theResource, String theResourceType, IIdType theId) {
409                        this(theRequestDetails, theResourceType, theId);
410                        myResource = theResource;
411                }
412
413                /**
414                 * Constructor
415                 * 
416                 * @param theRequestDetails
417                 *           The request details to wrap
418                 * @param theId
419                 *           The ID of the resource being created (note that the ID should have the resource type populated)
420                 */
421                public ActionRequestDetails(RequestDetails theRequestDetails, IIdType theId) {
422                        this(theRequestDetails, theId.getResourceType(), theId);
423                }
424
425                public ActionRequestDetails(RequestDetails theRequestDetails, String theResourceType, IIdType theId) {
426                        this(theRequestDetails, theRequestDetails.getServer().getFhirContext(), theResourceType, theId);
427                }
428
429                public FhirContext getContext() {
430                        return myContext;
431                }
432
433                /**
434                 * Returns the ID of the incoming request (typically this is from the request URL)
435                 */
436                public IIdType getId() {
437                        return myId;
438                }
439
440                /**
441                 * Returns the request details associated with this request
442                 */
443                public RequestDetails getRequestDetails() {
444                        return myRequestDetails;
445                }
446
447                /**
448                 * For requests where a resource is passed from the client to the server (e.g. create, update, etc.) this method
449                 * will return the resource which was provided by the client. Otherwise, this method will return <code>null</code>
450                 * .
451                 * <p>
452                 * Note that this method is currently only populated if the handling method has a parameter annotated with the
453                 * {@link ResourceParam} annotation.
454                 * </p>
455                 */
456                public IBaseResource getResource() {
457                        return myResource;
458                }
459
460                /**
461                 * Returns the resource type this request pertains to, or <code>null</code> if this request is not type specific
462                 * (e.g. server-history)
463                 */
464                public String getResourceType() {
465                        return myResourceType;
466                }
467
468                /**
469                 * Returns the same map which was
470                 */
471                public Map<Object, Object> getUserData() {
472                        if (myRequestDetails == null) {
473                                /*
474                                 * Technically this shouldn't happen.. But some of the unit tests use old IXXXDao methods that don't
475                                 * take in a RequestDetails object. Eventually I guess we should clean that up.
476                                 */
477                                return Collections.emptyMap();
478                        }
479                        return myRequestDetails.getUserData();
480                }
481
482                /**
483                 * This method may be invoked by user code to notify interceptors that a nested
484                 * operation is being invoked which is denoted by this request details.
485                 */
486                public void notifyIncomingRequestPreHandled(RestOperationTypeEnum theOperationType) {
487                        RequestDetails requestDetails = getRequestDetails();
488                        if (requestDetails == null) {
489                                return;
490                        }
491                        IRestfulServerDefaults server = requestDetails.getServer();
492                        if (server == null) {
493                                return;
494                        }
495                        List<IServerInterceptor> interceptors = server.getInterceptors();
496                        for (IServerInterceptor next : interceptors) {
497                                next.incomingRequestPreHandled(theOperationType, this);
498                        }
499                }
500
501                /**
502                 * This method should not be called by client code
503                 */
504                public void setResource(IBaseResource theObject) {
505                        myResource = theObject;
506                }
507
508        }
509
510}