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;
022
023import java.io.IOException;
024import java.io.PrintWriter;
025import java.io.StringWriter;
026import java.util.Locale;
027import java.util.ResourceBundle;
028
029import javax.faces.context.FacesContext;
030import javax.servlet.RequestDispatcher;
031import javax.servlet.ServletException;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletResponse;
034
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.common.utils.i18n.I18NUtils;
038import org.nuxeo.ecm.core.api.NuxeoException;
039import org.nuxeo.ecm.core.api.WrappedException;
040import org.nuxeo.ecm.platform.web.common.exceptionhandling.descriptor.ErrorHandler;
041
042/**
043 * @author arussel
044 */
045public class DefaultNuxeoExceptionHandler implements NuxeoExceptionHandler {
046
047    private static final Log log = LogFactory.getLog(DefaultNuxeoExceptionHandler.class);
048
049    protected NuxeoExceptionHandlerParameters parameters;
050
051    public void setParameters(NuxeoExceptionHandlerParameters parameters) {
052        this.parameters = parameters;
053    }
054
055    /**
056     * Puts a marker in request to avoid looping over the exception handling mechanism
057     *
058     * @throws ServletException if request has already been marked as handled. The initial exception is then wrapped.
059     */
060
061    protected void startHandlingException(HttpServletRequest request, HttpServletResponse response, Throwable t)
062            throws ServletException {
063        if (request.getAttribute(EXCEPTION_HANDLER_MARKER) == null) {
064            if (log.isDebugEnabled()) {
065                log.debug("Initial exception", t);
066            }
067            // mark request as already processed by this mechanism to avoid
068            // looping over it
069            request.setAttribute(EXCEPTION_HANDLER_MARKER, true);
070            // disable further redirect by nuxeo url system
071            request.setAttribute(DISABLE_REDIRECT_REQUEST_KEY, true);
072        } else {
073            // avoid looping over exception mechanism
074            throw new ServletException(t);
075        }
076    }
077
078    public void handleException(HttpServletRequest request, HttpServletResponse response, Throwable t)
079            throws IOException, ServletException {
080        startHandlingException(request, response, t);
081        try {
082            ErrorHandler handler = getHandler(t);
083            Integer code = handler.getCode();
084            int status = code == null ? HttpServletResponse.SC_INTERNAL_SERVER_ERROR : code.intValue();
085            parameters.getListener().startHandling(t, request, response);
086
087            Throwable unwrappedException = unwrapException(t);
088            StringWriter swriter = new StringWriter();
089            PrintWriter pwriter = new PrintWriter(swriter);
090            t.printStackTrace(pwriter);
091            String stackTrace = swriter.getBuffer().toString();
092            if (status < HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500
093                log.debug(t.getMessage(), t);
094            } else {
095                log.error(stackTrace);
096                parameters.getLogger().error(stackTrace);
097            }
098
099            parameters.getListener().beforeSetErrorPageAttribute(unwrappedException, request, response);
100            request.setAttribute("exception_message", unwrappedException.getLocalizedMessage());
101            request.setAttribute("user_message", getUserMessage(handler.getMessage(), request.getLocale()));
102            request.setAttribute("stackTrace", stackTrace);
103            request.setAttribute("securityError", ExceptionHelper.isSecurityError(unwrappedException));
104            request.setAttribute("messageBundle", ResourceBundle.getBundle(parameters.getBundleName(),
105                    request.getLocale(), Thread.currentThread().getContextClassLoader()));
106            String dumpedRequest = parameters.getRequestDumper().getDump(request);
107            if (status >= HttpServletResponse.SC_INTERNAL_SERVER_ERROR) { // 500
108                parameters.getLogger().error(dumpedRequest);
109            }
110            request.setAttribute("request_dump", dumpedRequest);
111
112            parameters.getListener().beforeForwardToErrorPage(unwrappedException, request, response);
113            if (!response.isCommitted()) {
114                response.setStatus(status);
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}