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.NullExceptionHandlingListener; 037import org.nuxeo.runtime.transaction.TransactionHelper; 038 039/** 040 * Plays with conversations, trying to rollback transaction. 041 * 042 * @author arussel 043 */ 044public class SeamExceptionHandlingListener extends NullExceptionHandlingListener { 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 /** 127 * Rollbacks transaction if necessary 128 */ 129 @Override 130 public void startHandling(Throwable t, HttpServletRequest request, HttpServletResponse response) 131 throws ServletException { 132 if (TransactionHelper.isTransactionActive()) { 133 TransactionHelper.setTransactionRollbackOnly(); 134 } 135 } 136 137 /** 138 * Cleans up context created in 139 * {@link #beforeSetErrorPageAttribute(Throwable, HttpServletRequest, HttpServletResponse)} when needed. 140 */ 141 @Override 142 public void afterDispatch(Throwable t, HttpServletRequest request, HttpServletResponse response) 143 throws IOException, ServletException { 144 FacesContext context = FacesContext.getCurrentInstance(); 145 // XXX: it's not clear what should be done here: current tests 146 // depending on the faces context just allow to avoid errors after 147 if (context instanceof MockFacesContext) { 148 // do not end the request if it's a real faces context, otherwise 149 // we'll get the following stack trace: 150 /** 151 * java.lang.IllegalStateException: No active application scope at 152 * org.jboss.seam.core.Init.instance(Init.java:76) at org.jboss.seam. 153 * jsf.SeamPhaseListener.handleTransactionsAfterPhase( SeamPhaseListener.java:330) at 154 * org.jboss.seam.jsf.SeamPhaseListener .afterServletPhase(SeamPhaseListener.java:241) at org.jboss.seam.jsf 155 * .SeamPhaseListener.afterPhase(SeamPhaseListener.java:192) at 156 * com.sun.faces.lifecycle.Phase.handleAfterPhase(Phase.java:175) at 157 * com.sun.faces.lifecycle.Phase.doPhase(Phase.java:114) at com.sun.faces 158 * .lifecycle.LifecycleImpl.render(LifecycleImpl.java:139) 159 */ 160 FacesLifecycle.endRequest(context.getExternalContext()); 161 // do not release an actual FacesContext that we did not create, 162 // otherwise we get the following stack trace: 163 /** 164 * java.lang.IllegalStateException at com.sun.faces.context.FacesContextImpl 165 * .assertNotReleased(FacesContextImpl.java:395) at com.sun.faces.context 166 * .FacesContextImpl.getExternalContext(FacesContextImpl.java:147) at 167 * com.sun.faces.util.RequestStateManager.getStateMap( RequestStateManager.java:276) at 168 * com.sun.faces.util.RequestStateManager .remove(RequestStateManager.java:243) at 169 * com.sun.faces.context.FacesContextImpl .release(FacesContextImpl.java:345) 170 */ 171 context.release(); 172 } 173 } 174 175 protected MockFacesContext createFacesContext(HttpServletRequest request, HttpServletResponse response) { 176 MockFacesContext mockFacesContext = new MockFacesContext(new MockExternalContext( 177 request.getSession().getServletContext(), request, response), new MockApplication()); 178 mockFacesContext.setViewRoot(new UIViewRoot()); 179 return mockFacesContext; 180 } 181 182}