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.webapp.shield;
018
019import java.io.IOException;
020
021import javax.faces.component.UIViewRoot;
022import javax.faces.context.FacesContext;
023import javax.servlet.ServletException;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029import org.jboss.seam.contexts.Contexts;
030import org.jboss.seam.contexts.FacesLifecycle;
031import org.jboss.seam.contexts.Lifecycle;
032import org.jboss.seam.mock.MockExternalContext;
033import org.jboss.seam.mock.MockFacesContext;
034import org.nuxeo.ecm.platform.web.common.exceptionhandling.service.NullExceptionHandlingListener;
035import org.nuxeo.runtime.transaction.TransactionHelper;
036
037/**
038 * Plays with conversations, trying to rollback transaction.
039 *
040 * @author arussel
041 */
042public class SeamExceptionHandlingListener extends NullExceptionHandlingListener {
043
044    private static final Log log = LogFactory.getLog(SeamExceptionHandlingListener.class);
045
046    /**
047     * Initiates a mock faces context when needed and tries to restore current conversation
048     */
049    @Override
050    public void beforeSetErrorPageAttribute(Throwable t, HttpServletRequest request, HttpServletResponse response)
051            throws IOException, ServletException {
052        // cut/paste from seam Exception filter.
053        // we recreate the seam context to be able to use the messages.
054
055        // patch following https://jira.jboss.org/browse/JBPAPP-1427
056        // Ensure that the call in which the exception occurred was cleaned up
057        // - it might not be, and there is no harm in trying
058
059        // we keep around the synchronization component because
060        // SeSynchronizations expects it to be unchanged when called by
061        // the finally block of UTTransaction.rollback
062        String SEAM_TX_SYNCS = "org.jboss.seam.transaction.synchronizations";
063        Object syncs = null;
064        if (Contexts.isEventContextActive()) {
065            syncs = Contexts.getEventContext().get(SEAM_TX_SYNCS);
066        }
067
068        Lifecycle.endRequest();
069
070        FacesContext facesContext = FacesContext.getCurrentInstance();
071        if (facesContext == null) {
072            // the FacesContext is gone - create a fake one for Redirect and
073            // HttpError to call
074            MockFacesContext mockContext = createFacesContext(request, response);
075            mockContext.setCurrent();
076            facesContext = mockContext;
077            log.debug("Created mock faces context for exception handling");
078        } else {
079            // do not use a mock faces context when a real one still exists:
080            // when released, the mock faces context is nullified, and this
081            // leads to errors like in the following stack trace:
082            /**
083             * java.lang.NullPointerException: FacesContext is null at org.ajax4jsf
084             * .context.AjaxContext.getCurrentInstance(AjaxContext.java:159) at
085             * org.ajax4jsf.context.AjaxContext.getCurrentInstance(AjaxContext. java:144) at
086             * org.ajax4jsf.component.AjaxViewRoot.getViewId(AjaxViewRoot .java:580) at
087             * com.sun.faces.lifecycle.Phase.doPhase(Phase.java:104)
088             */
089            log.debug("Using existing faces context for exception handling");
090        }
091
092        // NXP-5998: conversation initialization seems to be already handled by
093        // SeamPhaseListener => do not handle conversation as otherwise it'll
094        // stay unlocked.
095
096        // if the event context was cleaned up, fish the conversation id
097        // directly out of the ServletRequest attributes, else get it from
098        // the event context
099        // Manager manager = Contexts.isEventContextActive() ? (Manager)
100        // Contexts.getEventContext().get(
101        // Manager.class)
102        // : (Manager)
103        // request.getAttribute(Seam.getComponentName(Manager.class));
104        // String conversationId = manager == null ? null
105        // : manager.getCurrentConversationId();
106
107        FacesLifecycle.beginExceptionRecovery(facesContext.getExternalContext());
108
109        // restore syncs
110        if (syncs != null) {
111            Contexts.getEventContext().set(SEAM_TX_SYNCS, syncs);
112        }
113
114        // If there is an existing long-running conversation on
115        // the failed request, propagate it
116        // if (conversationId == null) {
117        // Manager.instance().initializeTemporaryConversation();
118        // } else {
119        // ConversationPropagation.instance().setConversationId(conversationId);
120        // Manager.instance().restoreConversation();
121        // }
122    }
123
124    /**
125     * Rollbacks transaction if necessary
126     */
127    @Override
128    public void startHandling(Throwable t, HttpServletRequest request, HttpServletResponse response)
129            throws ServletException {
130        if (TransactionHelper.isTransactionActive()) {
131            TransactionHelper.setTransactionRollbackOnly();
132        }
133    }
134
135    /**
136     * Cleans up context created in
137     * {@link #beforeSetErrorPageAttribute(Throwable, HttpServletRequest, HttpServletResponse)} when needed.
138     */
139    @Override
140    public void afterDispatch(Throwable t, HttpServletRequest request, HttpServletResponse response)
141            throws IOException, ServletException {
142        FacesContext context = FacesContext.getCurrentInstance();
143        // XXX: it's not clear what should be done here: current tests
144        // depending on the faces context just allow to avoid errors after
145        if (context instanceof MockFacesContext) {
146            // do not end the request if it's a real faces context, otherwise
147            // we'll get the following stack trace:
148            /**
149             * java.lang.IllegalStateException: No active application scope at
150             * org.jboss.seam.core.Init.instance(Init.java:76) at org.jboss.seam.
151             * jsf.SeamPhaseListener.handleTransactionsAfterPhase( SeamPhaseListener.java:330) at
152             * org.jboss.seam.jsf.SeamPhaseListener .afterServletPhase(SeamPhaseListener.java:241) at org.jboss.seam.jsf
153             * .SeamPhaseListener.afterPhase(SeamPhaseListener.java:192) at
154             * com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:175) at
155             * com.sun.faces.lifecycle.Phase.doPhase(Phase.java:114) at com.sun.faces
156             * .lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
157             */
158            FacesLifecycle.endRequest(context.getExternalContext());
159            // do not release an actual FacesContext that we did not create,
160            // otherwise we get the following stack trace:
161            /**
162             * java.lang.IllegalStateException at com.sun.faces.context.FacesContextImpl
163             * .assertNotReleased(FacesContextImpl.java:395) at com.sun.faces.context
164             * .FacesContextImpl.getExternalContext(FacesContextImpl.java:147) at
165             * com.sun.faces.util.RequestStateManager.getStateMap( RequestStateManager.java:276) at
166             * com.sun.faces.util.RequestStateManager .remove(RequestStateManager.java:243) at
167             * com.sun.faces.context.FacesContextImpl .release(FacesContextImpl.java:345)
168             */
169            context.release();
170        }
171    }
172
173    protected MockFacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) {
174        MockFacesContext mockFacesContext = new MockFacesContext(new MockExternalContext(
175                request.getSession().getServletContext(), request, response), new MockApplication());
176        mockFacesContext.setViewRoot(new UIViewRoot());
177        return mockFacesContext;
178    }
179
180}