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