/* * (C) Copyright 2006-2008 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * arussel */ package org.nuxeo.ecm.webapp.shield; import java.io.IOException; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.contexts.FacesLifecycle; import org.jboss.seam.contexts.Lifecycle; import org.jboss.seam.mock.MockExternalContext; import org.jboss.seam.mock.MockFacesContext; import org.nuxeo.ecm.platform.web.common.exceptionhandling.service.ExceptionHandlingListener; import org.nuxeo.runtime.transaction.TransactionHelper; /** * Plays with conversations, trying to rollback transaction. * * @author arussel */ public class SeamExceptionHandlingListener implements ExceptionHandlingListener { private static final Log log = LogFactory.getLog(SeamExceptionHandlingListener.class); /** * Initiates a mock faces context when needed and tries to restore current conversation */ @Override public void beforeSetErrorPageAttribute(Throwable t, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // cut/paste from seam Exception filter. // we recreate the seam context to be able to use the messages. // patch following https://jira.jboss.org/browse/JBPAPP-1427 // Ensure that the call in which the exception occurred was cleaned up // - it might not be, and there is no harm in trying // we keep around the synchronization component because // SeSynchronizations expects it to be unchanged when called by // the finally block of UTTransaction.rollback String SEAM_TX_SYNCS = "org.jboss.seam.transaction.synchronizations"; Object syncs = null; if (Contexts.isEventContextActive()) { syncs = Contexts.getEventContext().get(SEAM_TX_SYNCS); } Lifecycle.endRequest(); FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext == null) { // the FacesContext is gone - create a fake one for Redirect and // HttpError to call MockFacesContext mockContext = createFacesContext(request, response); mockContext.setCurrent(); facesContext = mockContext; log.debug("Created mock faces context for exception handling"); } else { // do not use a mock faces context when a real one still exists: // when released, the mock faces context is nullified, and this // leads to errors like in the following stack trace: /** * java.lang.NullPointerException: FacesContext is null at org.ajax4jsf * .context.AjaxContext.getCurrentInstance(AjaxContext.java:159) at * org.ajax4jsf.context.AjaxContext.getCurrentInstance(AjaxContext. java:144) at * org.ajax4jsf.component.AjaxViewRoot.getViewId(AjaxViewRoot .java:580) at * com.sun.faces.lifecycle.Phase.doPhase(Phase.java:104) */ log.debug("Using existing faces context for exception handling"); } // NXP-5998: conversation initialization seems to be already handled by // SeamPhaseListener => do not handle conversation as otherwise it'll // stay unlocked. // if the event context was cleaned up, fish the conversation id // directly out of the ServletRequest attributes, else get it from // the event context // Manager manager = Contexts.isEventContextActive() ? (Manager) // Contexts.getEventContext().get( // Manager.class) // : (Manager) // request.getAttribute(Seam.getComponentName(Manager.class)); // String conversationId = manager == null ? null // : manager.getCurrentConversationId(); FacesLifecycle.beginExceptionRecovery(facesContext.getExternalContext()); // restore syncs if (syncs != null) { Contexts.getEventContext().set(SEAM_TX_SYNCS, syncs); } // If there is an existing long-running conversation on // the failed request, propagate it // if (conversationId == null) { // Manager.instance().initializeTemporaryConversation(); // } else { // ConversationPropagation.instance().setConversationId(conversationId); // Manager.instance().restoreConversation(); // } } @Override public void beforeForwardToErrorPage(Throwable t, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // nothing } /** * Rollbacks transaction if necessary */ @Override public void startHandling(Throwable t, HttpServletRequest request, HttpServletResponse response) throws ServletException { if (TransactionHelper.isTransactionActive()) { TransactionHelper.setTransactionRollbackOnly(); } } /** * Cleans up context created in * {@link #beforeSetErrorPageAttribute(Throwable, HttpServletRequest, HttpServletResponse)} when needed. */ @Override public void afterDispatch(Throwable t, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { FacesContext context = FacesContext.getCurrentInstance(); // XXX: it's not clear what should be done here: current tests // depending on the faces context just allow to avoid errors after if (context instanceof MockFacesContext) { // do not end the request if it's a real faces context, otherwise // we'll get the following stack trace: /** * java.lang.IllegalStateException: No active application scope at * org.jboss.seam.core.Init.instance(Init.java:76) at org.jboss.seam. * jsf.SeamPhaseListener.handleTransactionsAfterPhase( SeamPhaseListener.java:330) at * org.jboss.seam.jsf.SeamPhaseListener .afterServletPhase(SeamPhaseListener.java:241) at org.jboss.seam.jsf * .SeamPhaseListener.afterPhase(SeamPhaseListener.java:192) at * com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:175) at * com.sun.faces.lifecycle.Phase.doPhase(Phase.java:114) at com.sun.faces * .lifecycle.LifecycleImpl.render(LifecycleImpl.java:139) */ FacesLifecycle.endRequest(context.getExternalContext()); // do not release an actual FacesContext that we did not create, // otherwise we get the following stack trace: /** * java.lang.IllegalStateException at com.sun.faces.context.FacesContextImpl * .assertNotReleased(FacesContextImpl.java:395) at com.sun.faces.context * .FacesContextImpl.getExternalContext(FacesContextImpl.java:147) at * com.sun.faces.util.RequestStateManager.getStateMap( RequestStateManager.java:276) at * com.sun.faces.util.RequestStateManager .remove(RequestStateManager.java:243) at * com.sun.faces.context.FacesContextImpl .release(FacesContextImpl.java:345) */ context.release(); } } protected MockFacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) { MockFacesContext mockFacesContext = new MockFacesContext(new MockExternalContext( request.getSession().getServletContext(), request, response), new MockApplication()); mockFacesContext.setViewRoot(new UIViewRoot()); return mockFacesContext; } @Override public void responseComplete() { FacesContext facesContext = FacesContext.getCurrentInstance(); if (facesContext != null) { facesContext.responseComplete(); } else { log.error("Cannot set response complete: faces context is null"); } } }