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}