001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.request.cycle; 018 019import java.util.Optional; 020 021import org.apache.wicket.Application; 022import org.apache.wicket.IMetadataContext; 023import org.apache.wicket.IWicketInternalException; 024import org.apache.wicket.MetaDataEntry; 025import org.apache.wicket.MetaDataKey; 026import org.apache.wicket.Page; 027import org.apache.wicket.Session; 028import org.apache.wicket.ThreadContext; 029import org.apache.wicket.WicketRuntimeException; 030import org.apache.wicket.core.request.handler.BookmarkablePageRequestHandler; 031import org.apache.wicket.core.request.handler.IPageProvider; 032import org.apache.wicket.core.request.handler.PageProvider; 033import org.apache.wicket.core.request.handler.RenderPageRequestHandler; 034import org.apache.wicket.event.IEvent; 035import org.apache.wicket.event.IEventSink; 036import org.apache.wicket.protocol.http.IRequestLogger; 037import org.apache.wicket.request.IExceptionMapper; 038import org.apache.wicket.request.IRequestCycle; 039import org.apache.wicket.request.IRequestHandler; 040import org.apache.wicket.request.IRequestMapper; 041import org.apache.wicket.request.Request; 042import org.apache.wicket.request.RequestHandlerExecutor; 043import org.apache.wicket.request.RequestHandlerExecutor.ReplaceHandlerException; 044import org.apache.wicket.request.Response; 045import org.apache.wicket.request.Url; 046import org.apache.wicket.request.UrlRenderer; 047import org.apache.wicket.request.component.IRequestablePage; 048import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler; 049import org.apache.wicket.request.handler.resource.ResourceRequestHandler; 050import org.apache.wicket.request.mapper.parameter.PageParameters; 051import org.apache.wicket.request.resource.IResource; 052import org.apache.wicket.request.resource.ResourceReference; 053import org.apache.wicket.request.resource.caching.IStaticCacheableResource; 054import org.apache.wicket.util.lang.Args; 055import org.apache.wicket.util.lang.Exceptions; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059/** 060 * {@link RequestCycle} consists of two steps: 061 * <ol> 062 * <li>Resolve request handler 063 * <li>Execute request handler 064 * </ol> 065 * During {@link IRequestHandler} execution the handler can schedule another {@link IRequestHandler} 066 * to run after it is done, or replace all {@link IRequestHandler}s on stack with another 067 * {@link IRequestHandler}. 068 * 069 * @see #scheduleRequestHandlerAfterCurrent(IRequestHandler) 070 * @see #replaceAllRequestHandlers(IRequestHandler) 071 * 072 * @author Matej Knopp 073 * @author igor.vaynberg 074 */ 075public class RequestCycle implements IRequestCycle, IEventSink, IMetadataContext<Object, RequestCycle> 076{ 077 private static final Logger log = LoggerFactory.getLogger(RequestCycle.class); 078 079 /** 080 * An additional logger which is used to log extra information. 081 * Could be disabled separately than the main logger if the application developer 082 * does not want to see this extra information. 083 */ 084 private static final Logger logExtra = LoggerFactory.getLogger("RequestCycleExtra"); 085 086 /** 087 * Returns request cycle associated with current thread. 088 * 089 * @return request cycle instance or <code>null</code> if no request cycle is associated with 090 * current thread. 091 */ 092 public static RequestCycle get() 093 { 094 return ThreadContext.getRequestCycle(); 095 } 096 097 /** 098 * 099 * @param requestCycle 100 */ 101 private static void set(RequestCycle requestCycle) 102 { 103 ThreadContext.setRequestCycle(requestCycle); 104 } 105 106 private Request request; 107 108 private final Response originalResponse; 109 110 private final IRequestMapper requestMapper; 111 112 private final IExceptionMapper exceptionMapper; 113 114 private final RequestCycleListenerCollection listeners; 115 116 private UrlRenderer urlRenderer; 117 118 /** MetaDataEntry array. */ 119 private MetaDataEntry<?>[] metaData; 120 121 /** the time that this request cycle object was created. */ 122 private final long startTime; 123 124 private final RequestHandlerExecutor requestHandlerExecutor; 125 126 private Response activeResponse; 127 128 /** 129 * Construct. 130 * 131 * @param context 132 */ 133 public RequestCycle(RequestCycleContext context) 134 { 135 Args.notNull(context, "context"); 136 Args.notNull(context.getRequest(), "context.request"); 137 Args.notNull(context.getResponse(), "context.response"); 138 Args.notNull(context.getRequestMapper(), "context.requestMapper"); 139 Args.notNull(context.getExceptionMapper(), "context.exceptionMapper"); 140 141 listeners = new RequestCycleListenerCollection(); 142 startTime = System.currentTimeMillis(); 143 requestHandlerExecutor = new HandlerExecutor(); 144 activeResponse = context.getResponse(); 145 request = context.getRequest(); 146 originalResponse = context.getResponse(); 147 requestMapper = context.getRequestMapper(); 148 exceptionMapper = context.getExceptionMapper(); 149 } 150 151 /** 152 * 153 * @return a new url renderer 154 */ 155 protected UrlRenderer newUrlRenderer() 156 { 157 // All URLs will be rendered relative to current request (can be overridden afterwards) 158 return new UrlRenderer(getRequest()); 159 } 160 161 /** 162 * Get the original response the request was created with. Access to the original response may 163 * be necessary if the response has been temporarily replaced but the components require methods 164 * from original response (i.e. cookie methods of WebResponse, etc). 165 * 166 * @return The original response object. 167 */ 168 public Response getOriginalResponse() 169 { 170 return originalResponse; 171 } 172 173 /** 174 * Returns {@link UrlRenderer} for this {@link RequestCycle}. 175 * 176 * @return UrlRenderer instance. 177 */ 178 @Override 179 public final UrlRenderer getUrlRenderer() 180 { 181 if (urlRenderer == null) 182 { 183 urlRenderer = newUrlRenderer(); 184 } 185 return urlRenderer; 186 } 187 188 /** 189 * Resolves current request to a {@link IRequestHandler}. 190 * 191 * @return RequestHandler instance 192 */ 193 protected IRequestHandler resolveRequestHandler() 194 { 195 return requestMapper.mapRequest(request); 196 } 197 198 /** 199 * @return How many times will Wicket attempt to render the exception request handler before 200 * giving up. 201 */ 202 protected int getExceptionRetryCount() 203 { 204 int retries = 10; 205 if (Application.exists()) 206 { 207 retries = Application.get().getRequestCycleSettings().getExceptionRetryCount(); 208 } 209 return retries; 210 } 211 212 /** 213 * Convenience method that processes the request and detaches the {@link RequestCycle}. 214 * 215 * @return <code>true</code> if the request resolved to a Wicket request, <code>false</code> 216 * otherwise. 217 */ 218 public boolean processRequestAndDetach() 219 { 220 boolean result; 221 try 222 { 223 result = processRequest(); 224 } 225 finally 226 { 227 detach(); 228 } 229 return result; 230 } 231 232 /** 233 * Processes the request. 234 * 235 * @return <code>true</code> if the request resolved to a Wicket request, <code>false</code> 236 * otherwise. 237 */ 238 public boolean processRequest() 239 { 240 try 241 { 242 set(this); 243 listeners.onBeginRequest(this); 244 onBeginRequest(); 245 IRequestHandler handler = resolveRequestHandler(); 246 if (handler == null) 247 { 248 // Did not find any suitable handler, thus not executing the request 249 log.debug( 250 "No suitable handler found for URL {}, falling back to container to process this request", 251 request.getUrl()); 252 } 253 else 254 { 255 execute(handler); 256 return true; 257 } 258 } 259 catch (Exception exception) 260 { 261 return executeExceptionRequestHandler(exception, getExceptionRetryCount()); 262 } 263 finally 264 { 265 try 266 { 267 listeners.onEndRequest(this); 268 onEndRequest(); 269 } 270 catch (RuntimeException e) 271 { 272 log.error("Exception occurred during onEndRequest", e); 273 } 274 275 set(null); 276 } 277 278 return false; 279 } 280 281 /** 282 * Execute a request handler and notify registered {@link IRequestCycleListener}s. 283 * 284 * @param handler 285 */ 286 private void execute(IRequestHandler handler) 287 { 288 Args.notNull(handler, "handler"); 289 290 while (handler != null) { 291 try 292 { 293 listeners.onRequestHandlerResolved(this, handler); 294 IRequestHandler next = requestHandlerExecutor.execute(handler); 295 listeners.onRequestHandlerExecuted(this, handler); 296 297 handler = next; 298 } 299 catch (RuntimeException e) 300 { 301 ReplaceHandlerException replacer = Exceptions.findCause(e, ReplaceHandlerException.class); 302 303 if (replacer == null) 304 { 305 throw e; 306 } 307 308 if (replacer.getRemoveScheduled()) 309 { 310 requestHandlerExecutor.schedule(null); 311 } 312 313 handler = replacer.getReplacementRequestHandler(); 314 } 315 } 316 } 317 318 /** 319 * Process the given exception. 320 * 321 * @param exception 322 * @param retryCount 323 */ 324 private boolean executeExceptionRequestHandler(Exception exception, int retryCount) 325 { 326 IRequestHandler handler = handleException(exception); 327 if (handler == null) 328 { 329 log.error("Error during request processing. URL=" + request.getUrl(), exception); 330 return false; 331 } 332 333 scheduleRequestHandlerAfterCurrent(null); 334 335 try 336 { 337 listeners.onExceptionRequestHandlerResolved(this, handler, exception); 338 339 execute(handler); 340 341 return true; 342 } 343 catch (Exception e) 344 { 345 if (retryCount > 0) 346 { 347 return executeExceptionRequestHandler(exception, retryCount - 1); 348 } 349 else 350 { 351 log.error("Exception retry count exceeded", e); 352 return false; 353 } 354 } 355 } 356 357 /** 358 * Return {@link IRequestHandler} for the given exception. 359 * 360 * @param e exception to handle 361 * @return RequestHandler instance 362 * 363 * @see IRequestCycleListener#onException(RequestCycle, Exception) 364 * @see IExceptionMapper#map(Exception) 365 */ 366 protected IRequestHandler handleException(final Exception e) 367 { 368 369 if (Application.exists() && Application.get().usesDevelopmentConfig()) 370 { 371 /* 372 * Call out the fact that we are processing an exception in a loud way, helps to notice 373 * them when developing even if they get wrapped or processed in a custom handler. 374 */ 375 logExtra.warn("********************************"); 376 logExtra.warn("Handling the following exception", e); 377 logExtra.warn("********************************"); 378 } 379 380 IRequestHandler handler = listeners.onException(this, e); 381 if (handler != null) 382 { 383 return handler; 384 } 385 return exceptionMapper.map(e); 386 } 387 388 /** 389 * @return current request 390 */ 391 @Override 392 public Request getRequest() 393 { 394 return request; 395 } 396 397 /** 398 * INTERNAL This method is for internal Wicket use. Do not call it yourself unless you know what 399 * you are doing. 400 * 401 * @param request 402 */ 403 public void setRequest(Request request) 404 { 405 // It would be mighty nice if request was final. However during multipart it needs to be set 406 // to 407 // MultipartServletWebRequest by Form. It can't be done before creating the request cycle 408 // (in wicket filter) 409 // because only the form knows max upload size 410 this.request = request; 411 } 412 413 /** 414 * Sets the metadata for this request cycle using the given key. If the metadata object is not 415 * of the correct type for the metadata key, an IllegalArgumentException will be thrown. For 416 * information on creating MetaDataKeys, see {@link MetaDataKey}. 417 * 418 * @param key 419 * The singleton key for the metadata 420 * @param object 421 * The metadata object 422 * @param <T> 423 * @throws IllegalArgumentException 424 * @see MetaDataKey 425 */ 426 @Override 427 public final <T> RequestCycle setMetaData(final MetaDataKey<T> key, final T object) 428 { 429 metaData = key.set(metaData, object); 430 return this; 431 } 432 433 /** 434 * Gets metadata for this request cycle using the given key. 435 * 436 * @param <T> 437 * The type of the metadata 438 * 439 * @param key 440 * The key for the data 441 * @return The metadata or null if no metadata was found for the given key 442 * @see MetaDataKey 443 */ 444 @Override 445 public final <T> T getMetaData(final MetaDataKey<T> key) 446 { 447 return key.get(metaData); 448 } 449 450 /** 451 * Returns URL for the request handler or <code>null</code> if the handler couldn't have been 452 * encoded. 453 * <p> 454 * <strong>Note</strong>: The produced URL is relative to the filter path. Application code most 455 * probably need URL relative to the currently used page, for this use 456 * {@linkplain #urlFor(org.apache.wicket.request.IRequestHandler)} 457 * </p> 458 * 459 * @param handler 460 * the {@link IRequestHandler request handler} for which to create a callback url 461 * @return Url instance or <code>null</code> 462 */ 463 public Url mapUrlFor(IRequestHandler handler) 464 { 465 final Url url = requestMapper.mapHandler(handler); 466 listeners.onUrlMapped(this, handler, url); 467 return url; 468 } 469 470 /** 471 * Returns a {@link Url} for the resource reference 472 * <p> 473 * <strong>Note</strong>: The produced URL is relative to the filter path. Application code most 474 * probably need URL relative to the currently used page, for this use 475 * {@linkplain #urlFor(org.apache.wicket.request.resource.ResourceReference, org.apache.wicket.request.mapper.parameter.PageParameters)} 476 * </p> 477 * 478 * @param reference 479 * resource reference 480 * @param params 481 * parameters for the resource or {@code null} if none 482 * @return {@link Url} for the reference 483 */ 484 public Url mapUrlFor(ResourceReference reference, PageParameters params) 485 { 486 return mapUrlFor(new ResourceReferenceRequestHandler(reference, params)); 487 } 488 489 /** 490 * Returns a bookmarkable URL that references a given page class using a given set of page 491 * parameters. Since the URL which is returned contains all information necessary to instantiate 492 * and render the page, it can be stored in a user's browser as a stable bookmark. 493 * <p> 494 * <strong>Note</strong>: The produced URL is relative to the filter path. Application code most 495 * probably need URL relative to the currently used page, for this use 496 * {@linkplain #urlFor(Class, org.apache.wicket.request.mapper.parameter.PageParameters)} 497 * </p> 498 * 499 * @param <C> 500 * The type of the page 501 * @param pageClass 502 * Class of page 503 * @param parameters 504 * Parameters to page or {@code null} if none 505 * @return Bookmarkable URL to page 506 */ 507 public final <C extends Page> Url mapUrlFor(final Class<C> pageClass, 508 final PageParameters parameters) 509 { 510 IRequestHandler handler = new BookmarkablePageRequestHandler(new PageProvider(pageClass, 511 parameters)); 512 return mapUrlFor(handler); 513 } 514 515 /** 516 * Returns a rendered {@link Url} for the resource reference 517 * 518 * @param reference 519 * resource reference 520 * @param params 521 * parameters for the resource or {@code null} if none 522 * @return {@link Url} for the reference 523 */ 524 public final CharSequence urlFor(ResourceReference reference, PageParameters params) 525 { 526 ResourceReferenceRequestHandler handler = new ResourceReferenceRequestHandler(reference, 527 params); 528 return urlFor(handler); 529 } 530 531 /** 532 * Returns a rendered bookmarkable URL that references a given page class using a given set of 533 * page parameters. Since the URL which is returned contains all information necessary to 534 * instantiate and render the page, it can be stored in a user's browser as a stable bookmark. 535 * 536 * @param <C> 537 * 538 * @param pageClass 539 * Class of page 540 * @param parameters 541 * Parameters to page or {@code null} if none 542 * @return Bookmarkable URL to page 543 */ 544 public final <C extends Page> CharSequence urlFor(final Class<C> pageClass, 545 final PageParameters parameters) 546 { 547 IRequestHandler handler = new BookmarkablePageRequestHandler(new PageProvider(pageClass, 548 parameters)); 549 return urlFor(handler); 550 } 551 552 /** 553 * Returns the rendered URL for the request handler or <code>null</code> if the handler couldn't 554 * have been rendered. 555 * <p> 556 * The resulting URL will be relative to current page. 557 * 558 * @param handler 559 * @return Url String or <code>null</code> 560 */ 561 public CharSequence urlFor(IRequestHandler handler) 562 { 563 try 564 { 565 Url mappedUrl = mapUrlFor(handler); 566 CharSequence url = renderUrl(mappedUrl, handler); 567 return url; 568 } 569 catch (Exception x) 570 { 571 throw new WicketRuntimeException(String.format( 572 "An error occurred while generating an Url for handler '%s'", handler), x); 573 } 574 575 } 576 577 private String renderUrl(Url url, IRequestHandler handler) 578 { 579 if (url != null) 580 { 581 boolean shouldEncodeStaticResource = Application.exists() && 582 Application.get().getResourceSettings().isEncodeJSessionId(); 583 584 String renderedUrl = getUrlRenderer().renderUrl(url); 585 if (handler instanceof ResourceReferenceRequestHandler) 586 { 587 ResourceReferenceRequestHandler rrrh = (ResourceReferenceRequestHandler)handler; 588 IResource resource = rrrh.getResource(); 589 if (resource != null && !(resource instanceof IStaticCacheableResource) || 590 shouldEncodeStaticResource) 591 { 592 renderedUrl = getOriginalResponse().encodeURL(renderedUrl); 593 } 594 } 595 else if (handler instanceof ResourceRequestHandler) 596 { 597 ResourceRequestHandler rrh = (ResourceRequestHandler)handler; 598 IResource resource = rrh.getResource(); 599 if (resource != null && !(resource instanceof IStaticCacheableResource) || 600 shouldEncodeStaticResource) 601 { 602 renderedUrl = getOriginalResponse().encodeURL(renderedUrl); 603 } 604 } 605 else 606 { 607 renderedUrl = getOriginalResponse().encodeURL(renderedUrl); 608 } 609 return renderedUrl; 610 } 611 else 612 { 613 return null; 614 } 615 } 616 617 /** 618 * Detaches {@link RequestCycle} state. Called after request processing is complete 619 */ 620 public final void detach() 621 { 622 set(this); 623 try 624 { 625 onDetach(); 626 } 627 finally 628 { 629 try 630 { 631 onInternalDetach(); 632 } 633 finally 634 { 635 set(null); 636 } 637 } 638 } 639 640 private void onInternalDetach() 641 { 642 if (Session.exists()) 643 { 644 Session.get().internalDetach(); 645 } 646 647 if (Application.exists()) 648 { 649 IRequestLogger requestLogger = Application.get().getRequestLogger(); 650 if (requestLogger != null) 651 requestLogger.performLogging(); 652 } 653 } 654 655 /** 656 * Called after request processing is complete, usually takes care of detaching state 657 */ 658 public void onDetach() 659 { 660 try 661 { 662 requestHandlerExecutor.detach(); 663 } 664 catch (RuntimeException exception) 665 { 666 handleDetachException(exception); 667 } 668 finally 669 { 670 listeners.onDetach(this); 671 } 672 673 if (Session.exists()) 674 { 675 Session.get().detach(); 676 } 677 678 } 679 680 /** 681 * Called to handle a {@link java.lang.RuntimeException} that might be 682 * thrown during detaching phase. 683 * 684 * @param exception 685 */ 686 private void handleDetachException(RuntimeException exception) 687 { 688 if (!(exception instanceof IWicketInternalException)) 689 { 690 log.error("Error detaching RequestCycle", exception); 691 } 692 } 693 694 /** 695 * Convenience method for setting next page to be rendered. 696 * 697 * @param page 698 */ 699 public void setResponsePage(IRequestablePage page) 700 { 701 if (page instanceof Page) 702 { 703 ((Page) page).setStatelessHint(false); 704 } 705 706 scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(new PageProvider(page), 707 RenderPageRequestHandler.RedirectPolicy.AUTO_REDIRECT)); 708 } 709 710 /** 711 * Convenience method for setting next page to be rendered. 712 * 713 * @param pageClass 714 * The class of the page to render 715 */ 716 public void setResponsePage(Class<? extends IRequestablePage> pageClass) 717 { 718 setResponsePage(pageClass, null, RenderPageRequestHandler.RedirectPolicy.ALWAYS_REDIRECT); 719 } 720 721 /** 722 * Convenience method for setting next page to be rendered. 723 * 724 * @param pageClass 725 * The class of the page to render 726 * @param redirectPolicy 727 * The policy to use when deciding whether to redirect or not 728 */ 729 public void setResponsePage(Class<? extends IRequestablePage> pageClass, RenderPageRequestHandler.RedirectPolicy redirectPolicy) 730 { 731 setResponsePage(pageClass, null, redirectPolicy); 732 } 733 734 /** 735 * Convenience method for setting next page to be rendered. 736 * 737 * @param pageClass 738 * The class of the page to render 739 * @param parameters 740 * The query parameters for the page to be rendered 741 */ 742 public void setResponsePage(Class<? extends IRequestablePage> pageClass, 743 PageParameters parameters) 744 { 745 setResponsePage(pageClass, parameters, RenderPageRequestHandler.RedirectPolicy.ALWAYS_REDIRECT); 746 } 747 748 /** 749 * Convenience method for setting next page to be rendered. 750 * 751 * @param pageClass 752 * The class of the page to render 753 * @param parameters 754 * The query parameters for the page to be rendered 755 * @param redirectPolicy 756 * The policy to use when deciding whether to redirect or not 757 */ 758 public void setResponsePage(Class<? extends IRequestablePage> pageClass, 759 PageParameters parameters, RenderPageRequestHandler.RedirectPolicy redirectPolicy) 760 { 761 IPageProvider provider = new PageProvider(pageClass, parameters); 762 scheduleRequestHandlerAfterCurrent(new RenderPageRequestHandler(provider, 763 redirectPolicy)); 764 } 765 766 /** 767 * @return The start time for this request 768 */ 769 public final long getStartTime() 770 { 771 return startTime; 772 } 773 774 /** {@inheritDoc} */ 775 @Override 776 public void onEvent(IEvent<?> event) 777 { 778 } 779 780 /** 781 * Called when the request cycle object is beginning its response 782 */ 783 protected void onBeginRequest() 784 { 785 } 786 787 /** 788 * Called when the request cycle object has finished its response 789 */ 790 protected void onEndRequest() 791 { 792 if (Session.exists()) 793 { 794 Session.get().endRequest(); 795 } 796 } 797 798 /** 799 * @return listeners 800 */ 801 public RequestCycleListenerCollection getListeners() 802 { 803 return listeners; 804 } 805 806 /** 807 * {@inheritDoc} 808 */ 809 @Override 810 public Response getResponse() 811 { 812 return activeResponse; 813 } 814 815 /** 816 * {@inheritDoc} 817 */ 818 @Override 819 public Response setResponse(final Response response) 820 { 821 Response current = activeResponse; 822 activeResponse = response; 823 return current; 824 } 825 826 /** 827 * {@inheritDoc} 828 */ 829 @Override 830 public void scheduleRequestHandlerAfterCurrent(IRequestHandler handler) 831 { 832 // just delegating the call to {@link IRequestHandlerExecutor} and invoking listeners 833 requestHandlerExecutor.schedule(handler); 834 835 // only forward calls to the listeners when handler is null 836 if (handler != null) 837 listeners.onRequestHandlerScheduled(this, handler); 838 } 839 840 /** 841 * @see RequestHandlerExecutor#getActive() 842 * @return active handler on executor 843 */ 844 public IRequestHandler getActiveRequestHandler() 845 { 846 return requestHandlerExecutor.getActive(); 847 } 848 849 /** 850 * @see RequestHandlerExecutor#next() 851 * @return the handler scheduled to be executed after current by the executor 852 */ 853 public IRequestHandler getRequestHandlerScheduledAfterCurrent() 854 { 855 return requestHandlerExecutor.next(); 856 } 857 858 /** 859 * @see RequestHandlerExecutor#replaceAll(IRequestHandler) 860 * @param handler 861 */ 862 public void replaceAllRequestHandlers(final IRequestHandler handler) 863 { 864 requestHandlerExecutor.replaceAll(handler); 865 } 866 867 /** 868 * Finds a IRequestHandler which is either the currently executing handler or is scheduled to be 869 * executed. 870 * 871 * @return the found IRequestHandler or {@link Optional#empty()} 872 */ 873 @SuppressWarnings("unchecked") 874 public <T extends IRequestHandler> Optional<T> find(final Class<T> type) 875 { 876 if (type == null) 877 { 878 return Optional.empty(); 879 } 880 881 IRequestHandler result = getActiveRequestHandler(); 882 if (type.isInstance(result)) 883 { 884 return (Optional<T>)Optional.of(result); 885 } 886 887 result = getRequestHandlerScheduledAfterCurrent(); 888 if (type.isInstance(result)) 889 { 890 return (Optional<T>)Optional.of(result); 891 } 892 893 return Optional.empty(); 894 } 895 896 /** 897 * Adapts {@link RequestHandlerExecutor} to this {@link RequestCycle} 898 * 899 * @author Igor Vaynberg 900 */ 901 private class HandlerExecutor extends RequestHandlerExecutor 902 { 903 904 @Override 905 protected void respond(IRequestHandler handler) 906 { 907 Response originalResponse = getResponse(); 908 try 909 { 910 handler.respond(RequestCycle.this); 911 } 912 finally 913 { 914 setResponse(originalResponse); 915 } 916 } 917 918 @Override 919 protected void detach(IRequestHandler handler) 920 { 921 handler.detach(RequestCycle.this); 922 } 923 924 } 925 926}