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}