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}