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.ui.web.shield;
018
019import java.io.IOException;
020import java.io.PrintWriter;
021import java.io.StringWriter;
022import java.util.Collection;
023import java.util.Enumeration;
024import java.util.Map;
025
026import javax.faces.component.UIViewRoot;
027import javax.servlet.ServletContext;
028import javax.servlet.ServletException;
029import javax.servlet.http.HttpServletRequest;
030import javax.servlet.http.HttpServletResponse;
031
032import org.apache.commons.logging.Log;
033import org.apache.commons.logging.LogFactory;
034import org.jboss.seam.Seam;
035import org.jboss.seam.contexts.Contexts;
036import org.jboss.seam.contexts.FacesLifecycle;
037import org.jboss.seam.core.ConversationPropagation;
038import org.jboss.seam.core.Manager;
039import org.jboss.seam.mock.MockApplication;
040import org.jboss.seam.mock.MockExternalContext;
041import org.jboss.seam.mock.MockFacesContext;
042import org.nuxeo.runtime.api.Framework;
043import org.nuxeo.runtime.model.RegistrationInfo;
044
045/**
046 * @author arussel
047 */
048public class ErrorPageForwarder {
049
050    private static final Log nuxeoErrorLog = LogFactory.getLog("nuxeo-error-log");
051
052    private static final Log log = LogFactory.getLog(ErrorPageForwarder.class);
053
054    private static final String SEAM_MESSAGES = "org.jboss.seam.international.messages";
055
056    private ServletContext servletContext;
057
058    public void forwardToErrorPage(HttpServletRequest request, HttpServletResponse response, Throwable t,
059            String exceptionMessage, String userMessage, Boolean securityError, ServletContext servletContext)
060            throws ServletException, IOException {
061        forwardToErrorPage(request, response, getStackTraceAsString(t), exceptionMessage, userMessage, securityError,
062                servletContext);
063    }
064
065    @SuppressWarnings("rawtypes")
066    public void forwardToErrorPage(HttpServletRequest request, HttpServletResponse response, String stackTrace,
067            String exceptionMessage, String userMessage, Boolean securityError, ServletContext servletContext)
068            throws ServletException, IOException {
069        log.error(stackTrace);
070        this.servletContext = servletContext;
071        // cut/paste from seam Exception filter.
072        // we recreate the seam context to be able to use the messages.
073        MockFacesContext facesContext = createFacesContext(request, response);
074        facesContext.setCurrent();
075
076        // if the event context was cleaned up, fish the conversation id
077        // directly out of the ServletRequest attributes, else get it from
078        // the event context
079        Manager manager = Contexts.isEventContextActive() ? (Manager) Contexts.getEventContext().get(Manager.class)
080                : (Manager) request.getAttribute(Seam.getComponentName(Manager.class));
081        String conversationId = manager == null ? null : manager.getCurrentConversationId();
082        FacesLifecycle.beginExceptionRecovery(facesContext.getExternalContext());
083        // If there is an existing long-running conversation on
084        // the failed request, propagate it
085        if (conversationId == null) {
086            Manager.instance().initializeTemporaryConversation();
087        } else {
088            ConversationPropagation.instance().setConversationId(conversationId);
089            Manager.instance().restoreConversation();
090        }
091        // we get the message from the seam attribute as the EL won't work in
092        // xhtml
093        String user_message = request.getAttribute(SEAM_MESSAGES) == null ? "An unexpected error occurred."
094                : (String) ((Map) request.getAttribute(SEAM_MESSAGES)).get(userMessage);
095        FacesLifecycle.beginExceptionRecovery(facesContext.getExternalContext());
096        request.setAttribute("exception_message", exceptionMessage);
097        request.setAttribute("user_message", user_message);
098        request.setAttribute("stackTrace", stackTrace);
099        request.setAttribute("securityError", securityError);
100        request.setAttribute("request_dump", getRequestDump(request));
101        request.getRequestDispatcher("/nuxeo_error.jsp").forward(request, response);
102        // FacesLifecycle.endRequest( facesContext.getExternalContext() );
103        // facesContext.release();
104    }
105
106    private String getRequestDump(HttpServletRequest request) {
107        StringBuilder builder = new StringBuilder();
108        builder.append("\nParameter:\n");
109        Map<String, String[]> m = request.getParameterMap();
110        for (Map.Entry<String, String[]> entry : m.entrySet()) {
111            builder.append(entry.getKey()).append(":");
112            if (entry.getValue() == null) {
113                continue;
114            }
115            for (String s : entry.getValue()) {
116                builder.append(s).append(",");
117            }
118            builder.deleteCharAt(builder.length() - 1);
119            builder.append("\n");
120        }
121        builder.append("\n");
122        Enumeration<String> names = request.getAttributeNames();
123        builder.append("Attributes:\n");
124        while (names.hasMoreElements()) {
125            String name = names.nextElement();
126            if (name.equals(SEAM_MESSAGES)) {
127                continue;
128            }
129            Object obj = request.getAttribute(name);
130            builder.append(name).append(": ").append(obj.toString()).append("\n");
131        }
132        builder.append("\n");
133        Collection<RegistrationInfo> infos = Framework.getRuntime().getComponentManager().getRegistrations();
134        builder.append("Components:\n");
135        for (RegistrationInfo info : infos) {
136            builder.append(info.getComponent().getName()).append(",").append(
137                    info.isActivated() ? "activated" : "not activated").append("\n");
138        }
139        nuxeoErrorLog.trace("User Principal: " + request.getUserPrincipal() + "\n" + builder.toString());
140        return builder.toString();
141    }
142
143    private MockFacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) {
144        MockFacesContext mockFacesContext = new MockFacesContext(new MockExternalContext(servletContext, request,
145                response), new MockApplication());
146        mockFacesContext.setViewRoot(new UIViewRoot());
147        return mockFacesContext;
148    }
149
150    public String getStackTraceAsString(Throwable t) {
151        StringWriter swriter = new StringWriter();
152        PrintWriter pwriter = new PrintWriter(swriter);
153        t.printStackTrace(pwriter);
154        return swriter.getBuffer().toString();
155    }
156
157}