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