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}