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;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025/**
026 * Manages executions of {@link IRequestHandler}s.
027 * 
028 * @author Matej Knopp
029 * @author igor.vaynberg
030 */
031public abstract class RequestHandlerExecutor
032{
033        private static final Logger log = LoggerFactory.getLogger(RequestHandlerExecutor.class);
034
035        private IRequestHandler active;
036
037        private final List<IRequestHandler> inactiveRequestHandlers = new ArrayList<>();
038
039        private IRequestHandler scheduledAfterCurrent = null;
040
041        /**
042         * Get the handler currently executed.
043         *
044         * @return active handler
045         */
046        public IRequestHandler getActive()
047        {
048                return active;
049        }
050
051        /**
052         * Execute the given handler.
053         *
054         * @param handler handler to be executed
055         * @return handler to be executed next
056         * @throws ReplaceHandlerException if another handler should replace the given handler for execution
057         */
058        public IRequestHandler execute(final IRequestHandler handler)
059        {
060                active = handler;
061                try
062                {
063                        respond(handler);
064                }
065                finally
066                {
067                        active = null;
068                        inactiveRequestHandlers.add(handler);
069                }
070
071                IRequestHandler scheduled = scheduledAfterCurrent;
072                scheduledAfterCurrent = null;
073                return scheduled;
074        }
075
076        /**
077         * Allows the request handler to respond to the request
078         * 
079         * @param handler
080         */
081        protected abstract void respond(IRequestHandler handler);
082
083        /**
084         * Schedules the handler after the current one
085         * 
086         * @param handler
087         */
088        public void schedule(final IRequestHandler handler)
089        {
090                scheduledAfterCurrent = handler;
091        }
092
093        /**
094         * @return scheduled request handler after the current one
095         */
096        public IRequestHandler next()
097        {
098                return scheduledAfterCurrent;
099        }
100
101        /**
102         * Replaces all request handlers on the stack with the specified one and executes it. If there
103         * are any request handlers currently executing (this method is called from inside
104         * {@link IRequestHandler#respond(IRequestCycle)}) the execution is interrupted via an
105         * exception.
106         * 
107         * @param handler
108         */
109        public void replaceAll(final IRequestHandler handler)
110        {
111                if (active == null)
112                {
113                        execute(handler);
114                }
115                else
116                {
117                        throw new ReplaceHandlerException(handler, true);
118                }
119        }
120
121        /**
122         * Detaches all request handlers
123         */
124        public void detach()
125        {
126                if (active != null)
127                {
128                        // no request handler should be active at this point
129                        log.warn("request handler is still active.");
130
131                        inactiveRequestHandlers.add(active);
132                        active = null;
133                }
134
135                RuntimeException rethrow = null;;
136                for (IRequestHandler handler : inactiveRequestHandlers)
137                {
138                        try
139                        {
140                                detach(handler);
141                        }
142                        catch (Exception exception)
143                        {
144                                if (rethrow == null && exception instanceof RuntimeException) {
145                                        rethrow = (RuntimeException) exception;
146                                } else {
147                                        log.error("Error detaching RequestHandler", exception);
148                                }
149                        }
150                }
151                if (rethrow != null) {
152                        // WICKET-6001 runtime exceptions are rethrown
153                        // TODO obsolete should component-queueing be removed
154                        throw rethrow;
155                }
156        }
157
158        /**
159         * Allows the request handler to detach
160         * 
161         * @param handler
162         */
163        protected abstract void detach(IRequestHandler handler);
164
165        /**
166         * Exception to stop current request handler and execute a new one.
167         * 
168         * @author Matej Knopp
169         */
170        public static class ReplaceHandlerException extends RuntimeException
171        {
172                private static final long serialVersionUID = 1L;
173
174                private final boolean removeScheduled;
175
176                private final IRequestHandler replacementRequestHandler;
177
178                /**
179                 * Construct.
180                 * 
181                 * @param replacementRequestHandler
182                 * @param removeScheduled should a possibly scheduled handler be removed
183                 */
184                public ReplaceHandlerException(final IRequestHandler replacementRequestHandler,
185                        final boolean removeScheduled)
186                {
187                        this.replacementRequestHandler = replacementRequestHandler;
188                        this.removeScheduled = removeScheduled;
189                }
190
191                /**
192                 * Should a scheduled handler be removed before replacing the handler
193                 */
194                public boolean getRemoveScheduled()
195                {
196                        return removeScheduled;
197                }
198
199                /**
200                 * @return the RequestHandler that should be used to continue handling the request
201                 */
202                public IRequestHandler getReplacementRequestHandler()
203                {
204                        return replacementRequestHandler;
205                }
206
207                /**
208                 * @see java.lang.Throwable#fillInStackTrace()
209                 */
210                @Override
211                public Throwable fillInStackTrace()
212                {
213                        // don't do anything here
214                        return null;
215                }
216        }
217}