/******************************************************************************
* JBoss, a division of Red Hat *
* Copyright 2006, Red Hat Middleware, LLC, and individual *
* contributors as indicated by the @authors tag. See the *
* copyright.txt in the distribution for a full listing of *
* individual contributors. *
* *
* This is free software; you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation; either version 2.1 of *
* the License, or (at your option) any later version. *
* *
* This software is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with this software; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
* 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
******************************************************************************/
package org.jboss.portletbridge.seam;
import static javax.faces.event.PhaseId.INVOKE_APPLICATION;
import static javax.faces.event.PhaseId.PROCESS_VALIDATIONS;
import static javax.faces.event.PhaseId.RENDER_RESPONSE;
import static javax.faces.event.PhaseId.RESTORE_VIEW;
import static org.jboss.seam.transaction.Transaction.TRANSACTION_FAILED;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.faces.application.FacesMessage;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.portlet.faces.Bridge;
import javax.portlet.faces.Bridge.PortletPhase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.portletbridge.BridgeRequestScope;
import org.jboss.portletbridge.context.PortletBridgeContext;
import org.jboss.portletbridge.richfaces.RichFacesStrategy;
import org.jboss.seam.Seam;
import org.jboss.seam.contexts.Context;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.contexts.FacesLifecycle;
import org.jboss.seam.core.ConversationPropagation;
import org.jboss.seam.core.Events;
import org.jboss.seam.core.Init;
import org.jboss.seam.core.Manager;
import org.jboss.seam.exception.Exceptions;
import org.jboss.seam.faces.FacesManager;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.faces.FacesPage;
import org.jboss.seam.faces.Switcher;
import org.jboss.seam.faces.Validation;
import org.jboss.seam.jsf.SeamPhaseListener;
import org.jboss.seam.navigation.Pages;
import org.jboss.seam.pageflow.Pageflow;
import org.jboss.seam.persistence.PersistenceContexts;
import org.jboss.seam.transaction.Transaction;
import org.jboss.seam.transaction.UserTransaction;
/**
* @author asmirnov
*
*/
public class SeamPhaseListenerWrapper implements PhaseListener {
private final SeamPhaseListener _defaultListener;
private static final Log log = LogFactory
.getLog(SeamPhaseListenerWrapper.class);
/**
* @param defaultListener
*/
public SeamPhaseListenerWrapper(PhaseListener defaultListener) {
_defaultListener = (SeamPhaseListener) defaultListener;
}
/**
*
*/
private static final long serialVersionUID = -8465467659533393697L;
/*
* (non-Javadoc)
*
* @see
* javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
public void afterPhase(PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
ExternalContext externalContext = facesContext.getExternalContext();
Bridge.PortletPhase portletPhase = (PortletPhase) externalContext
.getRequestMap().get(Bridge.PORTLET_LIFECYCLE_PHASE);
if (null != portletPhase) {
try {
raiseEventsAfterPhase(event);
afterPortletPhase(event, facesContext, portletPhase);
} finally {
FacesLifecycle.clearPhaseId();
}
} else {
_defaultListener.afterPhase(event);
}
}
protected void afterPortletPhase(PhaseEvent event,
FacesContext facesContext, PortletPhase portletPhase) {
PhaseId phaseId = event.getPhaseId();
if (phaseId == RESTORE_VIEW) {
afterRestoreView(facesContext);
} else if (phaseId == INVOKE_APPLICATION) {
afterInvokeApplication();
} else if (phaseId == PROCESS_VALIDATIONS) {
afterProcessValidations(facesContext);
}
FacesMessages.afterPhase();
// delegate to subclass:
handleTransactionsAfterPhase(event);
if (phaseId == RENDER_RESPONSE) {
// writeConversationIdToResponse(
// facesContext.getExternalContext().getResponse() );
if(isRichFacesResourceRequest(facesContext)){
finishRequest(facesContext);
} else {
afterRenderResponse(facesContext);
}
}
}
protected void finishRequest(FacesContext facesContext) {
// responseComplete() was called by one of the other phases,
// so we will never get to the RENDER_RESPONSE phase
// Note: we can't call Manager.instance().beforeRedirect() here,
// since a redirect is not the only reason for a responseComplete
PortletBridgeContext bridgeContext = PortletBridgeContext
.getCurrentInstance(facesContext);
if (null != bridgeContext) {
BridgeRequestScope windowState = bridgeContext.getRequestScope();
if (Contexts.isEventContextActive()) {
Manager manager = Manager.instance();
if (manager.isLongRunningConversation()) {
windowState.setConversationIdParameter(manager
.getConversationIdParameter());
windowState.setConversationId(manager
.getCurrentConversationId());
} else {
windowState.setConversationIdParameter(null);
windowState.setConversationId(null);
}
}
}
ExternalContext externalContext = facesContext.getExternalContext();
if (Contexts.isEventContextActive()) {
Manager.instance().endRequest(externalContext.getSessionMap());
}
FacesLifecycle.endRequest(facesContext.getExternalContext());
}
protected void saveFacesMessages(FacesContext facesContext) {
if (Contexts.isConversationContextActive()) {
PortletBridgeContext bridgeContext = PortletBridgeContext
.getCurrentInstance(facesContext);
if (null != bridgeContext) {
BridgeRequestScope windowState = bridgeContext
.getRequestScope();
// Exceptions.instance().handle(e);
org.jboss.portletbridge.seam.FacesMessages.afterPhase();
org.jboss.portletbridge.seam.FacesMessages messages = org.jboss.portletbridge.seam.FacesMessages
.instance();
List<FacesMessage> messageList = new ArrayList<FacesMessage>(messages.getStatusMessages());
Map<String, List<FacesMessage>> messagesMap = windowState.getMessages();
messagesMap.put("", messageList);
}
}
}
protected void afterRenderResponse(FacesContext facesContext) {
// do this both before and after render, since conversations
// and pageflows can begin during render
FacesManager.instance().prepareBackswitch(facesContext);
PersistenceContexts persistenceContexts = PersistenceContexts
.instance();
if (persistenceContexts != null) {
persistenceContexts.afterRender();
}
}
/**
* Restore the page and conversation contexts during a JSF request
*/
protected void afterRestoreView(FacesContext facesContext) {
FacesLifecycle.resumePage();
Map<String, String> parameters = facesContext.getExternalContext()
.getRequestParameterMap();
ConversationPropagation.instance().restoreConversationId(parameters);
boolean conversationFound = Manager.instance().restoreConversation();
FacesLifecycle.resumeConversation(facesContext.getExternalContext());
if(!isRichFacesResourceRequest(facesContext)){
afterRestorePage(facesContext, parameters, conversationFound);
}
}
protected boolean isRichFacesResourceRequest(FacesContext facesContext) {
return null != facesContext.getExternalContext().getRequestMap().get(RichFacesStrategy.RICHFACES_RESOURCE);
}
protected void afterRestorePage(FacesContext facesContext,
Map<String, String> parameters, boolean conversationFound) {
if (!Pages.isDebugPage()) {
// Only redirect to no-conversation-view if a login redirect isn't
// required
if (!conversationFound
&& !Pages.instance().isLoginRedirectRequired(facesContext)) {
Pages.instance().redirectToNoConversationView();
}
Manager.instance().handleConversationPropagation(parameters);
if (Init.instance().isJbpmInstalled()
&& !isExceptionHandlerRedirect()) {
Pageflow.instance().validatePageflow(facesContext);
}
Pages.instance().postRestore(facesContext);
}
}
protected boolean isExceptionHandlerRedirect() {
return Contexts.getConversationContext().isSet(
"org.jboss.seam.handledException");
}
protected void afterInvokeApplication() {
if (Init.instance().isTransactionManagementEnabled()) {
raiseTransactionFailedEvent();
}
}
protected void afterProcessValidations(FacesContext facesContext) {
Validation.instance().afterProcessValidations(facesContext);
}
protected void raiseEventsAfterPhase(PhaseEvent event) {
if (Contexts.isApplicationContextActive()) {
Events.instance().raiseEvent("org.jboss.seam.afterPhase", event);
}
}
/*
* (non-Javadoc)
*
* @see
* javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
public void beforePhase(PhaseEvent event) {
FacesContext facesContext = event.getFacesContext();
ExternalContext externalContext = facesContext.getExternalContext();
Bridge.PortletPhase portletPhase = (PortletPhase) externalContext
.getRequestMap().get(Bridge.PORTLET_LIFECYCLE_PHASE);
if (null != portletPhase) {
log.trace("before phase: " + event.getPhaseId());
FacesLifecycle.setPhaseId(event.getPhaseId());
try {
beforePortletPhase(event, facesContext, portletPhase);
raiseEventsBeforePhase(event);
} catch (Exception e) {
log.debug("uncaught exception, try to recovery", e);
try {
Exceptions.instance().handle(e);
} catch (Exception ehe) {
log.error("swallowing exception", e);
}
}
} else {
_defaultListener.beforePhase(event);
}
}
protected void beforePortletPhase(PhaseEvent event,
FacesContext facesContext, PortletPhase portletPhase) {
if(event.getPhaseId()== RESTORE_VIEW && isRichFacesResourceRequest(facesContext)){
beginRequest(facesContext);
}
// delegate to subclass:
handleTransactionsBeforePhase(event);
if (event.getPhaseId() == RENDER_RESPONSE) {
beforeRenderResponse(facesContext);
}
}
/**
* Set up the Seam contexts, except for the conversation context
*/
protected void beginRequest(FacesContext facesContext) {
FacesLifecycle.beginRequest(facesContext.getExternalContext());
}
protected void beforeRenderResponse(FacesContext facesContext) {
if (Contexts.isPageContextActive()) {
Context pageContext = Contexts.getPageContext();
// after every time that the view may have changed,
// we need to flush the page context, since the
// attribute map is being discarder
pageContext.flush();
// force refresh of the conversation lists (they are kept in PAGE
// context)
pageContext.remove(Seam.getComponentName(Switcher.class));
pageContext.remove("org.jboss.seam.core.conversationList");
pageContext.remove("org.jboss.seam.core.conversationStack");
}
performPageActions(facesContext);
if (facesContext.getResponseComplete()) {
// workaround for a bug in MyFaces prior to 1.1.3
if (Init.instance().isMyFacesLifecycleBug()) {
FacesLifecycle.endRequest(facesContext.getExternalContext());
}
} else // if the page actions did not call responseComplete()
{
FacesMessages.instance().beforeRenderResponse();
// do this both before and after render, since conversations
// and pageflows can begin during render
FacesManager.instance().prepareBackswitch(facesContext);
}
FacesPage.instance().storeConversation();
FacesPage.instance().storePageflow();
PersistenceContexts persistenceContexts = PersistenceContexts
.instance();
if (persistenceContexts != null) {
persistenceContexts.beforeRender();
}
}
protected boolean performPageActions(FacesContext facesContext) {
if (Pages.isDebugPage()) {
return false;
} else {
FacesLifecycle.setPhaseId(PhaseId.INVOKE_APPLICATION);
boolean actionsWereCalled = false;
try {
actionsWereCalled = Pages.instance().preRender(facesContext);
return actionsWereCalled;
} finally {
FacesLifecycle.setPhaseId(PhaseId.RENDER_RESPONSE);
if (actionsWereCalled) {
FacesMessages.afterPhase();
handleTransactionsAfterPageActions(facesContext); // TODO:
// does it
// really
// belong
// in the
// finally?
}
}
}
}
protected void raiseEventsBeforePhase(PhaseEvent event) {
if (Contexts.isApplicationContextActive()) {
Events.instance().raiseEvent("org.jboss.seam.beforePhase", event);
}
}
/**
* Raise an event so that an observer may add a faces message when
* Seam-managed transactions fail.
*/
protected void raiseTransactionFailedEvent() {
try {
UserTransaction tx = Transaction.instance();
if (tx.isRolledBackOrMarkedRollback()) {
if (Events.exists())
Events.instance().raiseEvent(TRANSACTION_FAILED,
tx.getStatus());
}
} catch (Exception e) {
} // swallow silently, not important
}
protected void handleTransactionsAfterPhase(PhaseEvent event) {
if (Init.instance().isTransactionManagementEnabled()) {
PhaseId phaseId = event.getPhaseId();
boolean commitTran = phaseId == PhaseId.INVOKE_APPLICATION
|| event.getFacesContext().getRenderResponse()
|| // TODO: no need to commit the tx if we failed to restore
// the view
event.getFacesContext().getResponseComplete()
|| phaseId == PhaseId.RENDER_RESPONSE;
// ( phaseId == PhaseId.RENDER_RESPONSE &&
// !Init.instance().isClientSideConversations() );
if (commitTran) {
commitOrRollback(phaseId); // we commit before destroying
// contexts, cos the contexts have
// the PC in them
}
}
}
protected void handleTransactionsAfterPageActions(FacesContext facesContext) {
if (Init.instance().isTransactionManagementEnabled()) {
commitOrRollback("after invoking page actions");
if (!facesContext.getResponseComplete()) {
begin("before continuing render");
}
}
}
protected void handleTransactionsBeforePhase(PhaseEvent event) {
if (Init.instance().isTransactionManagementEnabled()) {
PhaseId phaseId = event.getPhaseId();
boolean beginTran = phaseId == PhaseId.RENDER_RESPONSE
|| phaseId == (Transaction.instance()
.isConversationContextRequired() ? PhaseId.APPLY_REQUEST_VALUES
: PhaseId.RESTORE_VIEW);
// ( phaseId == PhaseId.RENDER_RESPONSE &&
// !Init.instance().isClientSideConversations() );
if (beginTran) {
begin(phaseId);
}
}
}
protected void begin(PhaseId phaseId) {
begin("prior to phase: " + phaseId);
}
protected void begin(String phaseString) {
try {
if (!Transaction.instance().isActiveOrMarkedRollback()) {
log.debug("beginning transaction " + phaseString);
Transaction.instance().begin();
}
} catch (Exception e) {
throw new IllegalStateException("Could not start transaction", e);
}
}
protected void commitOrRollback(PhaseId phaseId) {
commitOrRollback("after phase: " + phaseId);
}
protected void commitOrRollback(String phaseString) {
try {
if (Transaction.instance().isActive()) {
try {
log.debug("committing transaction " + phaseString);
Transaction.instance().commit();
} catch (IllegalStateException e) {
log.warn(
"TX commit failed with illegal state exception. This may be "
+ "because the tx timed out and was rolled back in the background.",
e);
}
} else if (Transaction.instance().isRolledBackOrMarkedRollback()) {
log.debug("rolling back transaction " + phaseString);
Transaction.instance().rollback();
}
} catch (Exception e) {
throw new IllegalStateException("Could not commit transaction", e);
}
}
/*
* (non-Javadoc)
*
* @see javax.faces.event.PhaseListener#getPhaseId()
*/
public PhaseId getPhaseId() {
return _defaultListener.getPhaseId();
}
}