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("stackTrace", stackTrace);
101        request.setAttribute("securityError", securityError);
102        request.setAttribute("request_dump", getRequestDump(request));
103        request.getRequestDispatcher("/nuxeo_error.jsp").forward(request, response);
104        // FacesLifecycle.endRequest( facesContext.getExternalContext() );
105        // facesContext.release();
106    }
107
108    private String getRequestDump(HttpServletRequest request) {
109        StringBuilder builder = new StringBuilder();
110        builder.append("\nParameter:\n");
111        Map<String, String[]> m = request.getParameterMap();
112        for (Map.Entry<String, String[]> entry : m.entrySet()) {
113            builder.append(entry.getKey()).append(":");
114            if (entry.getValue() == null) {
115                continue;
116            }
117            for (String s : entry.getValue()) {
118                builder.append(s).append(",");
119            }
120            builder.deleteCharAt(builder.length() - 1);
121            builder.append("\n");
122        }
123        builder.append("\n");
124        Enumeration<String> names = request.getAttributeNames();
125        builder.append("Attributes:\n");
126        while (names.hasMoreElements()) {
127            String name = names.nextElement();
128            if (name.equals(SEAM_MESSAGES)) {
129                continue;
130            }
131            Object obj = request.getAttribute(name);
132            builder.append(name).append(": ").append(obj.toString()).append("\n");
133        }
134        builder.append("\n");
135        Collection<RegistrationInfo> infos = Framework.getRuntime().getComponentManager().getRegistrations();
136        builder.append("Components:\n");
137        for (RegistrationInfo info : infos) {
138            builder.append(info.getComponent().getName()).append(",").append(
139                    info.isActivated() ? "activated" : "not activated").append("\n");
140        }
141        nuxeoErrorLog.trace("User Principal: " + request.getUserPrincipal() + "\n" + builder.toString());
142        return builder.toString();
143    }
144
145    private MockFacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) {
146        MockFacesContext mockFacesContext = new MockFacesContext(new MockExternalContext(servletContext, request,
147                response), new MockApplication());
148        mockFacesContext.setViewRoot(new UIViewRoot());
149        return mockFacesContext;
150    }
151
152    public String getStackTraceAsString(Throwable t) {
153        StringWriter swriter = new StringWriter();
154        PrintWriter pwriter = new PrintWriter(swriter);
155        t.printStackTrace(pwriter);
156        return swriter.getBuffer().toString();
157    }
158
159}