/*
* Copyright 2004 The Apache Software Foundation.
*
* 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.
*
* $Header:$
*/
package org.apache.beehive.netui.pageflow;
import org.apache.beehive.netui.util.logging.Logger;
import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
import org.apache.beehive.netui.pageflow.internal.InternalConstants;
import org.apache.beehive.netui.pageflow.internal.InternalUtils;
import org.apache.beehive.netui.pageflow.interceptor.action.InterceptorForward;
import org.apache.beehive.netui.pageflow.interceptor.action.ActionInterceptor;
import org.apache.beehive.netui.pageflow.interceptor.action.ActionInterceptorContext;
import org.apache.beehive.netui.pageflow.handler.Handlers;
import org.apache.beehive.netui.pageflow.handler.StorageHandler;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletContext;
import java.util.Stack;
import java.io.Serializable;
/**
* Stack for keeping track of a series of nested page flows. When a nested page flow is entered,
* the previous page flow is pushed onto this stack, which is kept in the user session.
*/
public class PageFlowStack
implements HttpSessionBindingListener, Serializable
{
private static final Logger _log = Logger.getInstance( PageFlowStack.class );
private static final String JPF_STACK_ATTR = InternalConstants.ATTR_PREFIX + "nestingStack";
private Stack _stack = new Stack();
private transient ServletContext _servletContext;
/**
* Wrapper that contains a pushed page flow and information related to it.
*/
public static class PushedPageFlow implements Serializable
{
private PageFlowController _pageFlow;
private ActionInterceptor _interceptor;
private InterceptorForward _interceptedForward;
private String _interceptedActionName;
public PushedPageFlow( PageFlowController pageFlow )
{
_pageFlow = pageFlow;
}
public PushedPageFlow( PageFlowController pageFlow, ActionInterceptor interceptor,
InterceptorForward interceptedFwd, String interceptedActionName )
{
this( pageFlow );
_interceptor = interceptor;
_interceptedForward = interceptedFwd;
_interceptedActionName = interceptedActionName;
}
public PageFlowController getPageFlow()
{
return _pageFlow;
}
public ActionInterceptor getInterceptor()
{
return _interceptor;
}
public InterceptorForward getInterceptedForward()
{
return _interceptedForward;
}
public String getInterceptedActionName()
{
return _interceptedActionName;
}
}
/**
* Get the stack of nested page flows for the current user session. Create and store an empty
* stack if none exists.
*
* @param request the current HttpServletRequest.
* @param servletContext the current ServletContext.
* @return the stack of nested page flows {@link PushedPageFlow}s) for the current user session.
*/
public static PageFlowStack get( HttpServletRequest request, ServletContext servletContext )
{
return get( request, servletContext, true );
}
/**
* Get the stack of nested page flows for the current user session. Create and store an empty
* stack if none exists.
* @deprecated Use {@link #get(HttpServletRequest, ServletContext)} instead.
*
* @param request the current HttpServletRequest.
* @return the stack of nested page flows {@link PushedPageFlow}s) for the current user session.
*/
public static PageFlowStack get( HttpServletRequest request )
{
return get( request, InternalUtils.getServletContext( request ) );
}
/**
* Get the stack of nested page flows for the current user session. Create and store an empty
* stack if none exists.
*
* @param request the current HttpServletRequest.
* @param servletContext the current ServletContext.
* @return a {@link PageFlowStack} of nested page flows ({@link PageFlowController}s) for the current user session.
*/
public static PageFlowStack get( HttpServletRequest request, ServletContext servletContext, boolean createIfNotExist )
{
StorageHandler sh = Handlers.get( servletContext ).getStorageHandler();
HttpServletRequest unwrappedRequest = PageFlowUtils.unwrapMultipart( request );
RequestContext rc = new RequestContext( unwrappedRequest, null );
String attrName = ScopedServletUtils.getScopedSessionAttrName( JPF_STACK_ATTR, unwrappedRequest );
PageFlowStack jpfStack = ( PageFlowStack ) sh.getAttribute( rc, attrName );
if ( jpfStack == null && createIfNotExist )
{
jpfStack = new PageFlowStack( servletContext );
jpfStack.save( request );
}
else if ( jpfStack != null )
{
jpfStack.setServletContext( servletContext );
}
return jpfStack;
}
/**
* Get the stack of nested page flows for the current user session. Create and store an empty
* stack if none exists.
* @deprecated Use {@link #get(HttpServletRequest, ServletContext, boolean)} instead.
*
* @param request the current HttpServletRequest
* @return a {@link PageFlowStack} of nested page flows ({@link PageFlowController}s) for the current user session.
*/
public static PageFlowStack get( HttpServletRequest request, boolean createIfNotExist )
{
return get( request, InternalUtils.getServletContext( request ), createIfNotExist );
}
/**
* Destroy the stack of {@link PageFlowController}s that have invoked nested page flows.
*
* @param request the current HttpServletRequest.
*/
public void destroy( HttpServletRequest request )
{
StorageHandler sh = Handlers.get( getServletContext() ).getStorageHandler();
HttpServletRequest unwrappedRequest = PageFlowUtils.unwrapMultipart( request );
RequestContext rc = new RequestContext( unwrappedRequest, null );
String attrName = ScopedServletUtils.getScopedSessionAttrName( JPF_STACK_ATTR, unwrappedRequest );
sh.removeAttribute( rc, attrName );
}
/**
* Pop page flows from the nesting stack until one of the given type is found.
*
* @return the last popped page flow if one of the given type was found, or <code>null</code>
* if none was found.
*/
PageFlowController popUntil( HttpServletRequest request, Class stopAt )
{
while ( ! isEmpty() )
{
PageFlowController popped = pop( request ).getPageFlow();
if ( popped.getClass().equals( stopAt ) )
{
//
// If we've popped everything from the stack, remove the stack attribute from the session.
//
if ( isEmpty() ) destroy( request );
return popped;
}
else
{
//
// We're discarding the popped page flow. Invoke its destroy() callback, unless it's longLived.
//
if ( ! popped.isLongLived() ) popped.destroy( request.getSession( false ) );
}
}
destroy( request ); // we're empty -- remove the attribute from the session.
return null;
}
/**
* Pop page flows from the nesting stack until the given index.
* @return the last popped page flow of the given type.
*/
PageFlowController popUntil( HttpServletRequest request, int index )
{
for ( int i = _stack.size() - 1; i > index; --i )
{
pop( request );
}
return pop( request ).getPageFlow();
}
int lastIndexOf( HttpServletRequest request, Class target )
{
for ( int i = _stack.size() - 1; i >= 0; --i )
{
if ( ( ( PushedPageFlow ) _stack.elementAt( i ) ).getPageFlow().getClass().equals( target ) )
{
return i;
}
}
return -1;
}
void ensureFailover( HttpServletRequest request, ServletContext servletContext )
{
StorageHandler sh = Handlers.get( servletContext ).getStorageHandler();
HttpServletRequest unwrappedRequest = PageFlowUtils.unwrapMultipart( request );
RequestContext rc = new RequestContext( unwrappedRequest, null );
String attrName = ScopedServletUtils.getScopedSessionAttrName( JPF_STACK_ATTR, unwrappedRequest );
sh.ensureFailover( rc, attrName, this );
}
void save( HttpServletRequest request )
{
StorageHandler sh = Handlers.get( getServletContext() ).getStorageHandler();
HttpServletRequest unwrappedRequest = PageFlowUtils.unwrapMultipart( request );
RequestContext rc = new RequestContext( unwrappedRequest, null );
String attrName = ScopedServletUtils.getScopedSessionAttrName( JPF_STACK_ATTR, unwrappedRequest );
sh.setAttribute( rc, attrName, this );
}
private PageFlowStack( ServletContext servletContext )
{
_servletContext = servletContext;
}
/**
* Push a page flow onto the stack of nested page flows in the session.
*
* @param pageFlow the page flow to push.
* @param request the current HttpServletRequest.
*/
public void push( PageFlowController pageFlow, HttpServletRequest request )
{
ActionInterceptorContext interceptorContext = ActionInterceptorContext.getActiveContext( request, true );
if ( interceptorContext != null )
{
ActionInterceptor interceptor = interceptorContext.getOverridingActionInterceptor();
InterceptorForward originalForward = interceptorContext.getOriginalForward();
String actionName = interceptorContext.getActionName();
_stack.push( new PushedPageFlow( pageFlow, interceptor, originalForward, actionName ) );
}
else
{
_stack.push( new PushedPageFlow( pageFlow ) );
}
// Tell the page flow that it is on the nesting stack.
pageFlow.setIsOnNestingStack( true );
// To ensure that this attribute is replicated for session failover.
ensureFailover( request, getServletContext() );
}
/**
* Pop the most recently-pushed page flow from the stack of nested page flows in the session.
*
* @param request the current HttpServletRequest.
* @return a {@link PushedPageFlow} that represents the popped page flow.
*/
public PushedPageFlow pop( HttpServletRequest request )
{
PushedPageFlow ppf = ( PushedPageFlow ) _stack.pop();
PageFlowController pfc = ppf.getPageFlow();
pfc.setIsOnNestingStack( false );
if ( request != null ) // may be null if we're called from valueUnbound()
{
ServletContext servletContext = getServletContext();
// Reinitialize the page flow, in case it's lost its transient state.
pfc.reinitialize( request, null, servletContext );
ensureFailover( request, servletContext ); // to ensure that this attribute is replicated for session failover
}
return ppf;
}
/**
* Get the most recently-pushed page flow from the stack of nested page flows in the session.
*
* @return a {@link PushedPageFlow} that represents the page flow at the top of the stack.
*/
public PushedPageFlow peek()
{
return ( PushedPageFlow ) _stack.peek();
}
/**
* Tell whether the stack of nested page flows is empty.
*
* @return <code>true</code> if there are no nested page flows on the stack.
*/
public boolean isEmpty()
{
return _stack.isEmpty();
}
/**
* Get the size of the stack of nested page flows.
*
* @return the number of page flows that are currently (hidden away) on the stack.
*/
public int size()
{
return _stack.size();
}
/**
* @exclude
*/
public void valueBound( HttpSessionBindingEvent event )
{
}
/**
* @exclude
*/
public void valueUnbound( HttpSessionBindingEvent event )
{
if ( _log.isDebugEnabled() )
{
_log.debug( "The page flow stack is being unbound from the session." );
}
while ( ! isEmpty() )
{
PageFlowController jpf = pop( null ).getPageFlow();
// Note that this page flow may have been serialized/deserialized, which will cause its transient info
// to be lost. Rehydrate it.
HttpSession session = event.getSession();
if ( session != null ) jpf.reinitialize( null, null, session.getServletContext() );
if ( ! jpf.isLongLived() ) jpf.destroy( event.getSession() );
}
}
/**
* Get a stack of PageFlowControllers, not of PushedPageFlows.
*/
Stack getLegacyStack()
{
Stack ret = new Stack();
for ( int i = 0; i < _stack.size(); ++i )
{
ret.push( ( ( PushedPageFlow ) _stack.get( i ) ).getPageFlow() );
}
return ret;
}
private ServletContext getServletContext()
{
return _servletContext;
}
private void setServletContext( ServletContext servletContext )
{
_servletContext = servletContext;
}
}