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}