package ca.uhn.fhir.rest.server.interceptor;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 - 2017 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
/**
* Provides methods to intercept requests and responses. Note that implementations of this interface may wish to use
* {@link InterceptorAdapter} in order to not need to implement every method.
* <p>
* <b>See:</b> See the <a href="http://jamesagnew.github.io/hapi-fhir/doc_rest_server_interceptor.html">server
* interceptor documentation</a> for more information on how to use this class.
* </p>
*/
public interface IServerInterceptor {
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes
* any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
* any runtime exceptions thrown by the server itself. This also includes any {@link AuthenticationException}s
* thrown.
* <p>
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>true</code>. In
* this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
* OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
* should return <code>false</code>, to indicate that they have handled the request and processing should stop.
* </p>
*
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
* properties are not all guaranteed to be populated, depending on how early during processing the
* exception occurred.
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
* <code>false</code> to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws ServletException
* If this exception is thrown, it will be re-thrown up to the container for handling.
* @throws IOException
* If this exception is thrown, it will be re-thrown up to the container for handling.
*/
boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws ServletException, IOException;
/**
* This method is called just before the actual implementing server method is invoked.
* <p>
* Note about exceptions:
* </p>
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theRequest
* The incoming request
* @param theResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
/**
* Invoked before an incoming request is processed. Note that this method is called
* after the server has begin preparing the response to the incoming client request.
* As such, it is not able to supply a response to the incoming request in the way that
* {@link #incomingRequestPreHandled(RestOperationTypeEnum, ActionRequestDetails)} and
* {@link #incomingRequestPostProcessed(RequestDetails, HttpServletRequest, HttpServletResponse)}
* are.
* <p>
* This method may however throw a subclass of {@link BaseServerResponseException}, and processing
* will be aborted with an appropriate error returned to the client.
* </p>
*
* @param theServletRequest
* The incoming servlet request as provided by the servlet container
* @param theOperation
* The type of operation that the FHIR server has determined that the client is trying to invoke
* @param theProcessedRequest
* An object which will be populated with the details which were extracted from the raw request by the
* server, e.g. the FHIR operation type and the parsed resource body (if any).
*/
void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest);
/**
* This method is called before any other processing takes place for each incoming request. It may be used to provide
* alternate handling for some requests, or to screen requests before they are handled, etc.
* <p>
* Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
* </p>
*
* @param theRequest
* The incoming request
* @param theResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
*/
boolean incomingRequestPreProcessed(HttpServletRequest theRequest, HttpServletResponse theResponse);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequest, Bundle theResponseObject);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
throws AuthenticationException;
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject);
/**
* This method is called after the server implementation method has been called, but before any attempt to stream the
* response back to the client
*
* @param theRequestDetails
* A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the {@link HttpServletRequest servlet request}.
* @param theResponseObject
* The actual object which is being streamed to the client as a response
* @param theServletRequest
* The incoming request
* @param theServletResponse
* The response. Note that interceptors may choose to provide a response (i.e. by calling
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
* to indicate that the server itself should not also provide a response.
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
* will be called.
* @throws AuthenticationException
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
*/
boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException;
/**
* This method is called upon any exception being thrown within the server's request processing code. This includes
* any exceptions thrown within resource provider methods (e.g. {@link Search} and {@link Read} methods) as well as
* any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them
* returns a non-<code>null</code> response or the end of the list is reached), after which
* {@link #handleException(RequestDetails, BaseServerResponseException, HttpServletRequest, HttpServletResponse)} is
* called for each interceptor.
* <p>
* This may be used to add an OperationOutcome to a response, or to convert between exception types for any reason.
* </p>
* <p>
* Implementations of this method may choose to ignore/log/count/etc exceptions, and return <code>null</code>. In
* this case, processing will continue, and the server will automatically generate an {@link BaseOperationOutcome
* OperationOutcome}. Implementations may also choose to provide their own response to the client. In this case, they
* should return a non-<code>null</code>, to indicate that they have handled the request and processing should stop.
* </p>
*
* @return Returns the new exception to use for processing, or <code>null</code> if this interceptor is not trying to
* modify the exception. For example, if this interceptor has nothing to do with exception processing, it
* should always return <code>null</code>. If this interceptor adds an OperationOutcome to the exception, it
* should return an exception.
*/
BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException;
/**
* This method is called after all processing is completed for a request, but only if the
* request completes normally (i.e. no exception is thrown).
* <p>
* Note that this individual interceptors will have this method called in the reverse order from the order in
* which the interceptors were registered with the server.
* </p>
* @param theRequestDetails
* The request itself
*/
void processingCompletedNormally(ServletRequestDetails theRequestDetails);
public static class ActionRequestDetails {
private final FhirContext myContext;
private final IIdType myId;
private RequestDetails myRequestDetails;
private IBaseResource myResource;
private final String myResourceType;
public ActionRequestDetails(RequestDetails theRequestDetails) {
myId = theRequestDetails.getId();
myResourceType = theRequestDetails.getResourceName();
myContext = theRequestDetails.getServer().getFhirContext();
myRequestDetails = theRequestDetails;
}
public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, IBaseResource theResource) {
this(theRequestDetails, theContext, theContext.getResourceDefinition(theResource).getName(), theResource.getIdElement());
myResource = theResource;
}
public ActionRequestDetails(RequestDetails theRequestDetails, FhirContext theContext, String theResourceType, IIdType theId) {
myId = theId;
myResourceType = theResourceType;
myContext = theContext;
myRequestDetails = theRequestDetails;
}
public ActionRequestDetails(RequestDetails theRequestDetails, IBaseResource theResource) {
this(theRequestDetails, theRequestDetails.getServer().getFhirContext().getResourceDefinition(theResource).getName(), theResource.getIdElement());
myResource = theResource;
}
public ActionRequestDetails(RequestDetails theRequestDetails, IBaseResource theResource, String theResourceType, IIdType theId) {
this(theRequestDetails, theResourceType, theId);
myResource = theResource;
}
/**
* Constructor
*
* @param theRequestDetails
* The request details to wrap
* @param theId
* The ID of the resource being created (note that the ID should have the resource type populated)
*/
public ActionRequestDetails(RequestDetails theRequestDetails, IIdType theId) {
this(theRequestDetails, theId.getResourceType(), theId);
}
public ActionRequestDetails(RequestDetails theRequestDetails, String theResourceType, IIdType theId) {
this(theRequestDetails, theRequestDetails.getServer().getFhirContext(), theResourceType, theId);
}
public FhirContext getContext() {
return myContext;
}
/**
* Returns the ID of the incoming request (typically this is from the request URL)
*/
public IIdType getId() {
return myId;
}
/**
* Returns the request details associated with this request
*/
public RequestDetails getRequestDetails() {
return myRequestDetails;
}
/**
* For requests where a resource is passed from the client to the server (e.g. create, update, etc.) this method
* will return the resource which was provided by the client. Otherwise, this method will return <code>null</code>
* .
* <p>
* Note that this method is currently only populated if the handling method has a parameter annotated with the
* {@link ResourceParam} annotation.
* </p>
*/
public IBaseResource getResource() {
return myResource;
}
/**
* Returns the resource type this request pertains to, or <code>null</code> if this request is not type specific
* (e.g. server-history)
*/
public String getResourceType() {
return myResourceType;
}
/**
* Returns the same map which was
*/
public Map<Object, Object> getUserData() {
if (myRequestDetails == null) {
/*
* Technically this shouldn't happen.. But some of the unit tests use old IXXXDao methods that don't
* take in a RequestDetails object. Eventually I guess we should clean that up.
*/
return Collections.emptyMap();
}
return myRequestDetails.getUserData();
}
/**
* This method may be invoked by user code to notify interceptors that a nested
* operation is being invoked which is denoted by this request details.
*/
public void notifyIncomingRequestPreHandled(RestOperationTypeEnum theOperationType) {
RequestDetails requestDetails = getRequestDetails();
if (requestDetails == null) {
return;
}
IRestfulServerDefaults server = requestDetails.getServer();
if (server == null) {
return;
}
List<IServerInterceptor> interceptors = server.getInterceptors();
for (IServerInterceptor next : interceptors) {
next.incomingRequestPreHandled(theOperationType, this);
}
}
/**
* This method should not be called by client code
*/
public void setResource(IBaseResource theObject) {
myResource = theObject;
}
}
}