001/* 002 * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * arussel 018 */ 019package org.nuxeo.ecm.platform.web.common.exceptionhandling; 020 021import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY; 022import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.FORCE_ANONYMOUS_LOGIN; 023import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.LOGOUT_PAGE; 024import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.REQUESTED_URL; 025import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.SECURITY_ERROR; 026 027import java.io.IOException; 028import java.io.PrintWriter; 029import java.io.StringWriter; 030import java.security.Principal; 031import java.util.HashMap; 032import java.util.Locale; 033import java.util.Map; 034import java.util.ResourceBundle; 035 036import javax.servlet.RequestDispatcher; 037import javax.servlet.ServletException; 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.common.utils.URIUtils; 044import org.nuxeo.common.utils.i18n.I18NUtils; 045import org.nuxeo.ecm.core.api.NuxeoException; 046import org.nuxeo.ecm.core.api.NuxeoPrincipal; 047import org.nuxeo.ecm.core.api.WrappedException; 048import org.nuxeo.ecm.platform.ui.web.auth.NuxeoAuthenticationFilter; 049import org.nuxeo.ecm.platform.ui.web.auth.service.PluggableAuthenticationService; 050import org.nuxeo.ecm.platform.web.common.exceptionhandling.descriptor.ErrorHandler; 051 052 053import org.nuxeo.runtime.api.Framework; 054 055/** 056 * @author arussel 057 */ 058public class DefaultNuxeoExceptionHandler implements NuxeoExceptionHandler { 059 060 private static final Log log = LogFactory.getLog(DefaultNuxeoExceptionHandler.class); 061 062 protected NuxeoExceptionHandlerParameters parameters; 063 064 @Override 065 public void setParameters(NuxeoExceptionHandlerParameters parameters) { 066 this.parameters = parameters; 067 } 068 069 /** 070 * Puts a marker in request to avoid looping over the exception handling mechanism 071 * 072 * @throws ServletException if request has already been marked as handled. The initial exception is then wrapped. 073 */ 074 075 protected void startHandlingException(HttpServletRequest request, HttpServletResponse response, Throwable t) 076 throws ServletException { 077 if (request.getAttribute(EXCEPTION_HANDLER_MARKER) == null) { 078 if (log.isDebugEnabled()) { 079 log.debug("Initial exception", t); 080 } 081 // mark request as already processed by this mechanism to avoid 082 // looping over it 083 request.setAttribute(EXCEPTION_HANDLER_MARKER, true); 084 // disable further redirect by nuxeo url system 085 request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true); 086 } else { 087 // avoid looping over exception mechanism 088 throw new ServletException(t); 089 } 090 } 091 092 @Override 093 public void handleException(HttpServletRequest request, HttpServletResponse response, Throwable t) 094 throws IOException, ServletException { 095 096 Throwable unwrappedException = ExceptionHelper.unwrapException(t); 097 098 // check for Anonymous case 099 if (ExceptionHelper.isSecurityError(unwrappedException)) { 100 Principal principal = request.getUserPrincipal(); 101 if (principal instanceof NuxeoPrincipal) { 102 NuxeoPrincipal nuxeoPrincipal = (NuxeoPrincipal) principal; 103 if (nuxeoPrincipal.isAnonymous()) { 104 // redirect to login than to requested page 105 if (handleAnonymousException(request, response)) { 106 return; 107 } 108 } 109 } 110 } 111 112 startHandlingException(request, response, t); 113 try { 114 ErrorHandler handler = getHandler(t); 115 Integer code = handler.getCode(); 116 int status = code == null ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : code.intValue(); 117 parameters.getListener().startHandling(t, request, response); 118 119 StringWriter swriter = new StringWriter(); 120 PrintWriter pwriter = new PrintWriter(swriter); 121 t.printStackTrace(pwriter); 122 String stackTrace = swriter.getBuffer().toString(); 123 if (status < HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500 124 log.debug(t.getMessage(), t); 125 } else { 126 log.error(stackTrace); 127 parameters.getLogger().error(stackTrace); 128 } 129 130 parameters.getListener().beforeSetErrorPageAttribute(unwrappedException, request, response); 131 request.setAttribute("exception_message", unwrappedException.getLocalizedMessage()); 132 request.setAttribute("user_message", getUserMessage(handler.getMessage(), request.getLocale())); 133 request.setAttribute("securityError", ExceptionHelper.isSecurityError(unwrappedException)); 134 request.setAttribute("messageBundle", ResourceBundle.getBundle(parameters.getBundleName(), 135 request.getLocale(), Thread.currentThread().getContextClassLoader())); 136 String dumpedRequest = parameters.getRequestDumper().getDump(request); 137 if (status >= HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500 138 parameters.getLogger().error(dumpedRequest); 139 } 140 request.setAttribute("isDevModeSet", Framework.isDevModeSet()); 141 if (Framework.isDevModeSet()) { 142 request.setAttribute("stackTrace", stackTrace); 143 request.setAttribute("request_dump", dumpedRequest); 144 } 145 146 parameters.getListener().beforeForwardToErrorPage(unwrappedException, request, response); 147 if (!response.isCommitted()) { 148 response.setStatus(status); 149 String errorPage = handler.getPage(); 150 errorPage = (errorPage == null) ? parameters.getDefaultErrorPage() : errorPage; 151 RequestDispatcher requestDispatcher = request.getRequestDispatcher(errorPage); 152 if (requestDispatcher != null) { 153 requestDispatcher.forward(request, response); 154 } else { 155 log.error("Cannot forward to error page, " + "no RequestDispatcher found for errorPage=" 156 + errorPage + " handler=" + handler); 157 } 158 parameters.getListener().responseComplete(); 159 } else { 160 // do not throw an error, just log it: afterDispatch needs to 161 // be called, and sometimes the initial error is a 162 // ClientAbortException 163 log.error("Cannot forward to error page: " + "response is already committed"); 164 } 165 parameters.getListener().afterDispatch(unwrappedException, request, response); 166 } catch (ServletException e) { 167 throw e; 168 } catch (RuntimeException | IOException e) { 169 throw new ServletException(e); 170 } 171 } 172 173 @Override 174 public boolean handleAnonymousException(HttpServletRequest request, HttpServletResponse response) 175 throws IOException, ServletException { 176 PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime().getComponent( 177 PluggableAuthenticationService.NAME); 178 if (authService == null) { 179 return false; 180 } 181 authService.invalidateSession(request); 182 String loginURL = getLoginURL(request); 183 if (loginURL == null) { 184 return false; 185 } 186 if (!response.isCommitted()) { 187 request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true); 188 response.sendRedirect(loginURL); 189 parameters.getListener().responseComplete(); 190 } else { 191 log.error("Cannot redirect to login page: response is already committed"); 192 } 193 return true; 194 } 195 196 @Override 197 public String getLoginURL(HttpServletRequest request) { 198 PluggableAuthenticationService authService = (PluggableAuthenticationService) Framework.getRuntime().getComponent( 199 PluggableAuthenticationService.NAME); 200 Map<String, String> urlParameters = new HashMap<String, String>(); 201 urlParameters.put(SECURITY_ERROR, "true"); 202 urlParameters.put(FORCE_ANONYMOUS_LOGIN, "true"); 203 if (request.getAttribute(REQUESTED_URL) != null) { 204 urlParameters.put(REQUESTED_URL, (String) request.getAttribute(REQUESTED_URL)); 205 } else { 206 urlParameters.put(REQUESTED_URL, NuxeoAuthenticationFilter.getRequestedUrl(request)); 207 } 208 String baseURL = authService.getBaseURL(request) + LOGOUT_PAGE; 209 return URIUtils.addParametersToURIQuery(baseURL, urlParameters); 210 } 211 212 protected ErrorHandler getHandler(Throwable t) { 213 Throwable throwable = unwrapException(t); 214 String className = null; 215 if (throwable instanceof WrappedException) { 216 WrappedException wrappedException = (WrappedException) throwable; 217 className = wrappedException.getClassName(); 218 } else { 219 className = throwable.getClass().getName(); 220 } 221 for (ErrorHandler handler : parameters.getHandlers()) { 222 if (handler.getError() != null && className.matches(handler.getError())) { 223 return handler; 224 } 225 } 226 throw new NuxeoException("No error handler set."); 227 } 228 229 protected Object getUserMessage(String messageKey, Locale locale) { 230 return I18NUtils.getMessageString(parameters.getBundleName(), messageKey, null, locale); 231 } 232 233 /** 234 * @deprecated use {@link ExceptionHelper#unwrapException(Throwable)} 235 */ 236 @Deprecated 237 public static Throwable unwrapException(Throwable t) { 238 return ExceptionHelper.unwrapException(t); 239 } 240 241}