/*
* 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.internal;
import org.apache.beehive.netui.pageflow.handler.StorageHandler;
import org.apache.beehive.netui.pageflow.RequestContext;
import org.apache.beehive.netui.pageflow.ServletContainerAdapter;
import org.apache.beehive.netui.pageflow.scoping.ScopedServletUtils;
import org.apache.beehive.netui.util.logging.Logger;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionBindingEvent;
import java.util.HashSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.Collections;
/**
* This alternate session storage handler does not write any attribute into the session until the very end of a chain
* of forwarded requests (i.e., not even at the end of an inner forwarded request). This allows it to handle multiple
* concurrent forwarded requests, each of which is modifying the same data, in a more reasonable way. Basically,
* each request works in its own snapshot of the session, and the last one to commit is the one whose snapshot wins.
* This is a better alternative thatn allowing them to interfere with each other in the middle of the request chain.
*/
public class DeferredSessionStorageHandler
extends DefaultHandler
implements StorageHandler
{
private static final Logger _log = Logger.getInstance(DeferredSessionStorageHandler.class);
private static final String CHANGELIST_ATTR = InternalConstants.ATTR_PREFIX + "changedAttrs";
private static final String FAILOVER_MAP_ATTR = InternalConstants.ATTR_PREFIX + "failoverAttrs";
private static ThreadLocal _isCommittingChanges =
new ThreadLocal()
{
public Object initialValue()
{
return Boolean.FALSE;
}
};
public DeferredSessionStorageHandler( ServletContext servletContext )
{
init( null, null, servletContext );
}
private static final class SessionBindingEvent
extends HttpSessionBindingEvent
{
public SessionBindingEvent( HttpSession httpSession, String attrName )
{
super( httpSession, attrName );
}
public SessionBindingEvent( HttpSession httpSession, String attrName, Object attrVal )
{
super( httpSession, attrName, attrVal );
}
}
public void setAttribute( RequestContext context, String attrName, Object value )
{
if (_log.isTraceEnabled()) {
_log.trace("setAttribute: " + attrName + "=" + value);
}
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
Object currentValue = request.getAttribute( attrName );
//
// Note that we unconditionally get/create the session. We want to make sure that the session exists when
// we set an attribute, since we will *not* create it in applyChanges, to prevent recreation of an
// intentionally-invalidated session.
//
HttpSession session = request.getSession();
//
// Emulate a setAttribute on the session: if the value is an HttpSessionBindingListener, invoke its
// valueUnbound(). Note that we don't currently care about calling valueBound().
//
if ( currentValue != null && currentValue != value && currentValue instanceof HttpSessionBindingListener )
{
HttpSessionBindingEvent event = new SessionBindingEvent( session, attrName, currentValue );
( ( HttpSessionBindingListener ) currentValue ).valueUnbound( event );
}
request.setAttribute( attrName, value );
getChangedAttributesList( request, true, false ).add( attrName );
HashMap failoverAttrs = getFailoverAttributesMap( request, false, false );
if ( failoverAttrs != null ) failoverAttrs.remove( attrName );
}
public void removeAttribute( RequestContext context, String attrName )
{
if (_log.isTraceEnabled()) {
_log.trace("removeAttribute: " + attrName);
}
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
Object currentValue = request.getAttribute( attrName );
//
// Emulate a removeAttribute on the session: if the value is an HttpSessionBindingListener, invoke its
// valueUnbound().
//
if ( currentValue != null && currentValue instanceof HttpSessionBindingListener )
{
HttpSessionBindingEvent event = new SessionBindingEvent( request.getSession(), attrName, currentValue );
( ( HttpSessionBindingListener ) currentValue ).valueUnbound( event );
}
request.removeAttribute( attrName );
getChangedAttributesList( request, true, false ).add( attrName );
HashMap failoverAttrs = getFailoverAttributesMap( request, false, false );
if ( failoverAttrs != null ) failoverAttrs.remove( attrName );
}
public Object getAttribute( RequestContext context, String attributeName )
{
if (_log.isTraceEnabled()) {
_log.trace("getAttribute: " + attributeName);
}
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
Object val = request.getAttribute( attributeName );
if ( val != null ) return val;
// If the attribute isn't present in the request and is in the list of changed attrs, then it was removed.
// Don't fall back to the session attribute in that case.
HashSet changedAttrs = getChangedAttributesList( request, false, false );
if ( changedAttrs != null && changedAttrs.contains( attributeName ) ) return null;
// Get the attribute out of the session, and put it into the request. Until applyChanges is called, this is
// the value we'll use.
HttpSession session = request.getSession( false );
if ( session != null )
{
val = session.getAttribute( attributeName );
if ( val != null ) request.setAttribute( attributeName, val );
}
return val;
}
public void applyChanges( RequestContext context )
{
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
HttpSession session = request.getSession( false );
if (_log.isDebugEnabled()) {
_log.debug("Applying changes for request " + request.getRequestURI());
}
//
// If the session doesn't exist, we don't want to recreate it. It has most likely been intentionally
// invalidated, since we did ensure its existance in getAttribute.
//
if ( session == null ) return;
HashSet changedAttrs = getChangedAttributesList( request, false, true );
if ( changedAttrs != null )
{
//
// Go through each changed attribute, and either write it to the session or remove it to the session,
// depending on whether or not it exists in the request.
//
for ( Iterator i = changedAttrs.iterator(); i.hasNext(); )
{
String attrName = ( String ) i.next();
Object val = request.getAttribute( attrName );
if ( val != null )
{
// Write it to the session, but only if the current value isn't already this value.
Object currentValue = session.getAttribute( attrName );
if ( currentValue != val )
{
if (_log.isTraceEnabled()) {
_log.trace("Committing attribute " + attrName + " to the session.");
}
// This ThreadLocal value allows others (e.g., an HttpSessionBindingListener like
// PageFlowManagedObject) that we're in the middle of committing changes to the session.
_isCommittingChanges.set( Boolean.TRUE );
try
{
session.setAttribute( attrName, val );
}
finally
{
_isCommittingChanges.set( Boolean.FALSE );
}
request.removeAttribute( attrName );
}
}
else
{
if (_log.isTraceEnabled()) {
_log.trace("Removing attribute " + attrName + " from the session.");
}
//
// This ThreadLocal value allows others (e.g., an HttpSessionBindingListener like
// PageFlowManagedObject) that we're in the middle of committing changes to the session.
//
_isCommittingChanges.set( Boolean.TRUE );
try
{
session.removeAttribute( attrName );
}
finally
{
_isCommittingChanges.set( Boolean.FALSE );
}
}
}
}
//
// Now go through the attributes we need to ensure-failover on.
//
HashMap failoverAttrs = getFailoverAttributesMap( request, false, true );
if ( failoverAttrs != null )
{
ServletContainerAdapter sa = AdapterManager.getServletContainerAdapter( getServletContext() );
for ( Iterator i = failoverAttrs.entrySet().iterator(); i.hasNext(); )
{
Map.Entry entry = ( Map.Entry ) i.next();
if (_log.isTraceEnabled()) {
_log.trace("Ensure failover for attribute " + entry.getKey());
}
sa.ensureFailover( ( String ) entry.getKey(), entry.getValue(), request );
}
}
}
public void dropChanges(RequestContext context)
{
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
if (_log.isDebugEnabled()) {
_log.debug("Dropping changes for request " + request.getRequestURI());
}
getChangedAttributesList( request, false, true );
getFailoverAttributesMap( request, false, true );
}
public Enumeration getAttributeNames(RequestContext context) {
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
HttpSession session = request.getSession( false );
ArrayList attrNames = new ArrayList();
// Start with the attribute names that are in the session.
if (session != null) {
for (Enumeration e = session.getAttributeNames(); e.hasMoreElements(); ) {
attrNames.add((String) e.nextElement());
}
}
// Add or remove as necessary, based on the list of changed attributes.
HashSet changedAttrs = getChangedAttributesList( request, false, false );
if (changedAttrs != null) {
for (Iterator i = changedAttrs.iterator(); i.hasNext(); ) {
String attrName = (String) i.next();
if (request.getAttribute(attrName) != null) {
attrNames.add(attrName);
} else {
// If the attribute isn't present in the request and is in the session, then it's scheduled for
// removal.
attrNames.remove(attrName);
}
}
}
return Collections.enumeration(attrNames);
}
public void ensureFailover( RequestContext context, String attributeName, Object value )
{
HttpServletRequest request = ScopedServletUtils.getOuterRequest( ( HttpServletRequest ) context.getRequest() );
getFailoverAttributesMap( request, true, false ).put( attributeName, value );
}
private static HashSet getChangedAttributesList( ServletRequest request, boolean create, boolean remove )
{
HashSet set = ( HashSet ) request.getAttribute( CHANGELIST_ATTR );
if ( set == null && create )
{
set = new HashSet();
request.setAttribute( CHANGELIST_ATTR, set );
}
if ( set!= null && remove ) request.removeAttribute( CHANGELIST_ATTR );
return set;
}
private static HashMap getFailoverAttributesMap( ServletRequest request, boolean create, boolean remove )
{
HashMap map = ( HashMap ) request.getAttribute( FAILOVER_MAP_ATTR );
if ( map == null && create )
{
map = new HashMap();
request.setAttribute( FAILOVER_MAP_ATTR, map );
}
if ( map != null && remove ) request.removeAttribute( FAILOVER_MAP_ATTR );
return map;
}
public boolean allowBindingEvent( Object event )
{
return ! ( ( Boolean ) _isCommittingChanges.get() ).booleanValue();
}
}