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;
018
019import org.apache.wicket.authorization.AuthorizationException;
020import org.apache.wicket.core.request.handler.IPageRequestHandler;
021import org.apache.wicket.core.request.handler.ListenerInvocationNotAllowedException;
022import org.apache.wicket.core.request.handler.PageProvider;
023import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
024import org.apache.wicket.core.request.mapper.StalePageException;
025import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
026import org.apache.wicket.protocol.http.PageExpiredException;
027import org.apache.wicket.protocol.http.servlet.ResponseIOException;
028import org.apache.wicket.request.IExceptionMapper;
029import org.apache.wicket.request.IRequestHandler;
030import org.apache.wicket.request.Request;
031import org.apache.wicket.request.Response;
032import org.apache.wicket.request.cycle.RequestCycle;
033import org.apache.wicket.request.handler.EmptyRequestHandler;
034import org.apache.wicket.request.http.WebRequest;
035import org.apache.wicket.request.http.WebResponse;
036import org.apache.wicket.request.http.handler.ErrorCodeRequestHandler;
037import org.apache.wicket.request.resource.PackageResource;
038import org.apache.wicket.settings.ExceptionSettings;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042/**
043 * If an exception is thrown when a page is being rendered this mapper will decide which error page
044 * to show depending on the exception type and {@link Application#getExceptionSettings() application
045 * configuration}
046 */
047public class DefaultExceptionMapper implements IExceptionMapper
048{
049        private static final Logger logger = LoggerFactory.getLogger(DefaultExceptionMapper.class);
050
051        @Override
052        public IRequestHandler map(Exception e)
053        {
054                try
055                {
056                        Response response = RequestCycle.get().getResponse();
057                        // WICKET-7067 WebSocketResponse doesn't allow setting headers
058                        if (response instanceof WebResponse && ((WebResponse) response).isHeaderSupported())
059                        {
060                                // we don't want to cache an exceptional reply in the browser
061                                ((WebResponse) response).disableCaching();
062                        }
063                        return internalMap(e);
064                }
065                catch (RuntimeException e2)
066                {
067                        return handleNestedException(e, e2);
068                }
069        }
070
071
072        /**
073         * Handles the case when an exception is generated while mapping the original exception happened
074         *
075         * @param originalException The original exception.
076         * @param nestedException The nested (second) exception produced
077         * @return IRequestHandler (by default ErrorCodeRequestHandler
078         */
079        protected IRequestHandler handleNestedException(Exception originalException, RuntimeException nestedException)
080        {
081                if (logger.isDebugEnabled())
082                {
083                        logger.error(
084                                        "An error occurred while handling a previous error: " + nestedException.getMessage(), nestedException);
085                }
086
087                // hmmm, we were already handling an exception! give up
088                logger.error("unexpected exception when handling another exception: " + originalException.getMessage(),
089                                originalException);
090                return new ErrorCodeRequestHandler(500);
091        }
092        
093        /**
094         * Maps exceptions to their corresponding {@link IRequestHandler}.
095         * 
096         * @param e
097         *                      the current exception
098         * @return the {@link IRequestHandler} for the current exception
099         */
100        private IRequestHandler internalMap(Exception e)
101        {
102                final Application application = Application.get();
103
104                // check if we are processing an Ajax request and if we want to invoke the failure handler
105                if (isProcessingAjaxRequest())
106                {
107                        switch (application.getExceptionSettings().getAjaxErrorHandlingStrategy())
108                        {
109                                case INVOKE_FAILURE_HANDLER :
110                                        return new ErrorCodeRequestHandler(500);
111                        }
112                }
113
114                IRequestHandler handleExpectedExceptions = mapExpectedExceptions(e, application);
115                
116                if (handleExpectedExceptions != null)
117                {
118                        return handleExpectedExceptions;
119                }
120
121                return mapUnexpectedExceptions(e, application);
122        
123        }
124        
125        /**
126         * Maps expected exceptions (i.e. those internally used by Wicket) to their corresponding
127         * {@link IRequestHandler}.
128         * 
129         * @param e
130         *                      the current exception
131         * @param application
132         *                      the current application object
133         * @return the {@link IRequestHandler} for the current exception
134         */
135        protected IRequestHandler mapExpectedExceptions(Exception e, final Application application)
136        {
137                if (e instanceof StalePageException)
138                {
139                        // If the page was stale, just re-render it
140                        // (the url should always be updated by an redirect in that case)
141                        return new RenderPageRequestHandler(new PageProvider(((StalePageException)e).getPage()));
142                }
143                else if (e instanceof PageExpiredException)
144                {
145                        return createPageRequestHandler(new PageProvider(Application.get()
146                                .getApplicationSettings()
147                                .getPageExpiredErrorPage()));
148                }
149                else if (e instanceof AuthorizationException ||
150                        e instanceof ListenerInvocationNotAllowedException)
151                {
152                        return createPageRequestHandler(new PageProvider(Application.get()
153                                .getApplicationSettings()
154                                .getAccessDeniedPage()));
155                }
156                else if (e instanceof ResponseIOException)
157                {
158                        logger.debug("Connection lost, give up responding.", e);
159                        return new EmptyRequestHandler();
160                }
161                else if (e instanceof PackageResource.PackageResourceBlockedException && application.usesDeploymentConfig())
162                {
163                        logger.debug(e.getMessage(), e);
164                        return new ErrorCodeRequestHandler(404);
165                }
166                
167                return null;
168        }
169
170        /**
171         * Maps unexpected exceptions to their corresponding {@link IRequestHandler}.
172         * 
173         * @param e
174         *                      the current exception
175         * @param application
176         *                      the current application object
177         * @return the {@link IRequestHandler} for the current exception
178         */
179        protected IRequestHandler mapUnexpectedExceptions(Exception e, final Application application)
180        {
181                final ExceptionSettings.UnexpectedExceptionDisplay unexpectedExceptionDisplay = application.getExceptionSettings()
182                        .getUnexpectedExceptionDisplay();
183
184                logger.error("Unexpected error occurred", e);
185
186                if (ExceptionSettings.SHOW_EXCEPTION_PAGE.equals(unexpectedExceptionDisplay))
187                {
188                        Page currentPage = extractCurrentPage();
189                        return createPageRequestHandler(new PageProvider(new ExceptionErrorPage(e,
190                                currentPage)));
191                }
192                else if (ExceptionSettings.SHOW_INTERNAL_ERROR_PAGE.equals(unexpectedExceptionDisplay))
193                {
194                        return createPageRequestHandler(new PageProvider(
195                                application.getApplicationSettings().getInternalErrorPage()));
196                }
197                
198                // IExceptionSettings.SHOW_NO_EXCEPTION_PAGE
199                return new ErrorCodeRequestHandler(500);
200        }
201
202        /**
203         * Creates a {@link RenderPageRequestHandler} for the target page provided by {@code pageProvider}.
204         * <p>
205         * Uses {@link RenderPageRequestHandler.RedirectPolicy#NEVER_REDIRECT} policy to preserve the original page's URL
206         * for non-Ajax requests and {@link RenderPageRequestHandler.RedirectPolicy#AUTO_REDIRECT} for AJAX requests.
207         * 
208         * @param pageProvider
209         *                      the page provider for the target page
210         * @return the request handler for the target page
211         */
212        protected RenderPageRequestHandler createPageRequestHandler(PageProvider pageProvider)
213        {
214                RequestCycle requestCycle = RequestCycle.get();
215
216                if (requestCycle == null)
217                {
218                        throw new IllegalStateException(
219                                "there is no current request cycle attached to this thread");
220                }
221
222                RenderPageRequestHandler.RedirectPolicy redirect = RenderPageRequestHandler.RedirectPolicy.NEVER_REDIRECT;
223
224                if (isProcessingAjaxRequest())
225                {
226                        redirect = RenderPageRequestHandler.RedirectPolicy.AUTO_REDIRECT;
227                }
228
229                return new RenderPageRequestHandler(pageProvider, redirect);
230        }
231        
232        /**
233         * @return true if current request is an AJAX request, false otherwise.
234         */
235        protected boolean isProcessingAjaxRequest()
236        {
237                RequestCycle rc = RequestCycle.get();
238                Request request = rc.getRequest();
239                if (request instanceof WebRequest)
240                {
241                        return ((WebRequest)request).isAjax();
242                }
243                return false;
244        }
245
246        /**
247         * @return the page being rendered when the exception was thrown, or {@code null} if it cannot
248         *         be extracted
249         */
250        protected Page extractCurrentPage()
251        {
252                final RequestCycle requestCycle = RequestCycle.get();
253
254                IRequestHandler handler = requestCycle.getActiveRequestHandler();
255
256                if (handler == null)
257                {
258                        handler = requestCycle.getRequestHandlerScheduledAfterCurrent();
259                }
260
261                if (handler instanceof IPageRequestHandler)
262                {
263                        IPageRequestHandler pageRequestHandler = (IPageRequestHandler)handler;
264                        return (Page)pageRequestHandler.getPage();
265                }
266                return null;
267        }
268}