001/*
002 * (C) Copyright 2006-2008 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     arussel
016 */
017package org.nuxeo.ecm.platform.web.common.exceptionhandling;
018
019import static org.nuxeo.ecm.platform.ui.web.auth.NXAuthConstants.DISABLE_REDIRECT_REQUEST_KEY;
020
021import java.io.IOException;
022import java.io.PrintWriter;
023import java.io.StringWriter;
024import java.util.Locale;
025import java.util.ResourceBundle;
026
027import javax.faces.context.FacesContext;
028import javax.servlet.RequestDispatcher;
029import javax.servlet.ServletException;
030import javax.servlet.http.HttpServletRequest;
031import javax.servlet.http.HttpServletResponse;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.common.utils.i18n.I18NUtils;
036import org.nuxeo.ecm.core.api.NuxeoException;
037import org.nuxeo.ecm.core.api.WrappedException;
038import org.nuxeo.ecm.platform.web.common.exceptionhandling.descriptor.ErrorHandler;
039
040/**
041 * @author arussel
042 */
043public class DefaultNuxeoExceptionHandler implements NuxeoExceptionHandler {
044
045    private static final Log log = LogFactory.getLog(DefaultNuxeoExceptionHandler.class);
046
047    protected NuxeoExceptionHandlerParameters parameters;
048
049    public void setParameters(NuxeoExceptionHandlerParameters parameters) {
050        this.parameters = parameters;
051    }
052
053    /**
054     * Puts a marker in request to avoid looping over the exception handling mechanism
055     *
056     * @throws ServletException if request has already been marked as handled. The initial exception is then wrapped.
057     */
058
059    protected void startHandlingException(HttpServletRequest request, HttpServletResponse response, Throwable t)
060            throws ServletException {
061        if (request.getAttribute(EXCEPTION_HANDLER_MARKER) == null) {
062            if (log.isDebugEnabled()) {
063                log.debug("Initial exception", t);
064            }
065            // mark request as already processed by this mechanism to avoid
066            // looping over it
067            request.setAttribute(EXCEPTION_HANDLER_MARKER, true);
068            // disable further redirect by nuxeo url system
069            request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true);
070        } else {
071            // avoid looping over exception mechanism
072            throw new ServletException(t);
073        }
074    }
075
076    public void handleException(HttpServletRequest request, HttpServletResponse response, Throwable t)
077            throws IOException, ServletException {
078        startHandlingException(request, response, t);
079        try {
080            ErrorHandler handler = getHandler(t);
081            Integer code = handler.getCode();
082            boolean is404 = Integer.valueOf(404).equals(code);
083            parameters.getListener().startHandling(t, request, response);
084
085            Throwable unwrappedException = unwrapException(t);
086            StringWriter swriter = new StringWriter();
087            PrintWriter pwriter = new PrintWriter(swriter);
088            t.printStackTrace(pwriter);
089            String stackTrace = swriter.getBuffer().toString();
090            if (is404) {
091                log.debug(t.getMessage());
092            } else {
093                log.error(stackTrace);
094                parameters.getLogger().error(stackTrace);
095            }
096
097            parameters.getListener().beforeSetErrorPageAttribute(unwrappedException, request, response);
098            request.setAttribute("exception_message", unwrappedException.getLocalizedMessage());
099            request.setAttribute("user_message", getUserMessage(handler.getMessage(), request.getLocale()));
100            request.setAttribute("stackTrace", stackTrace);
101            request.setAttribute("securityError", ExceptionHelper.isSecurityError(unwrappedException));
102            request.setAttribute("messageBundle", ResourceBundle.getBundle(parameters.getBundleName(),
103                    request.getLocale(), Thread.currentThread().getContextClassLoader()));
104            String dumpedRequest = parameters.getRequestDumper().getDump(request);
105            if (!is404) {
106                parameters.getLogger().error(dumpedRequest);
107            }
108            request.setAttribute("request_dump", dumpedRequest);
109
110            parameters.getListener().beforeForwardToErrorPage(unwrappedException, request, response);
111            if (!response.isCommitted()) {
112                if (code != null) {
113                    response.setStatus(code);
114                }
115                String errorPage = handler.getPage();
116                errorPage = (errorPage == null) ? parameters.getDefaultErrorPage() : errorPage;
117                RequestDispatcher requestDispatcher = request.getRequestDispatcher(errorPage);
118                if (requestDispatcher != null) {
119                    requestDispatcher.forward(request, response);
120                } else {
121                    log.error("Cannot forward to error page, " + "no RequestDispatcher found for errorPage="
122                            + errorPage + " handler=" + handler);
123                }
124                FacesContext fContext = FacesContext.getCurrentInstance();
125                if (fContext != null) {
126                    fContext.responseComplete();
127                } else {
128                    log.error("Cannot set response complete: faces context is null");
129                }
130            } else {
131                // do not throw an error, just log it: afterDispatch needs to
132                // be called, and sometimes the initial error is a
133                // ClientAbortException
134                log.error("Cannot forward to error page: " + "response is already committed");
135            }
136            parameters.getListener().afterDispatch(unwrappedException, request, response);
137        } catch (ServletException e) {
138            throw e;
139        } catch (RuntimeException | IOException e) {
140            throw new ServletException(e);
141        }
142    }
143
144    protected ErrorHandler getHandler(Throwable t) {
145        Throwable throwable = unwrapException(t);
146        String className = null;
147        if (throwable instanceof WrappedException) {
148            WrappedException wrappedException = (WrappedException) throwable;
149            className = wrappedException.getClassName();
150        } else {
151            className = throwable.getClass().getName();
152        }
153        for (ErrorHandler handler : parameters.getHandlers()) {
154            if (handler.getError() != null && className.matches(handler.getError())) {
155                return handler;
156            }
157        }
158        throw new NuxeoException("No error handler set.");
159    }
160
161    protected Object getUserMessage(String messageKey, Locale locale) {
162        return I18NUtils.getMessageString(parameters.getBundleName(), messageKey, null, locale);
163    }
164
165    /**
166     * @deprecated use {@link ExceptionHelper#unwrapException(Throwable)}
167     */
168    @Deprecated
169    public static Throwable unwrapException(Throwable t) {
170        return ExceptionHelper.unwrapException(t);
171    }
172
173}