/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file 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.web.tomcat.service.session;
import java.util.HashMap;
import java.util.Map;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.jboss.web.tomcat.service.session.distributedcache.spi.OutgoingDistributableSessionData;
public final class SessionReplicationContext
{
private static final ThreadLocal<SessionReplicationContext> replicationContext = new ThreadLocal<SessionReplicationContext>();
private static final SessionReplicationContext EMPTY = new SessionReplicationContext();
private int webappCount;
// private int activityCount;
private SnapshotManager soleManager;
private ClusteredSession<? extends OutgoingDistributableSessionData> soleSession;
private Map<ClusteredSession<? extends OutgoingDistributableSessionData>, SnapshotManager> crossCtxSessions;
private Request outerRequest;
private Response outerResponse;
/**
* Associate a SessionReplicationContext with the current thread, if
* there isn't one already. If there isn't one, associate the
* given request and response with the context.
* <p/>
* <strong>NOTE:</strong> Nested calls to this method and {@link #exitWebapp()}
* are supported; once a context is established the number of calls to this
* method and <code>exitWebapp()</code> are tracked.
*
* @param request
* @param response
*/
public static void enterWebapp(Request request, Response response, boolean startCacheActivity)
{
SessionReplicationContext ctx = getCurrentContext();
if (ctx == null)
{
ctx = new SessionReplicationContext(request, response);
replicationContext.set(ctx);
}
ctx.webappCount++;
}
/**
* Signals that the webapp is finished handling the request (and
* therefore replication can begin.)
*
* @return a SessionReplicationContext, from which information
* about any sessions needing replication can be obtained.
* Will not return <code>null</code>.
*/
public static SessionReplicationContext exitWebapp()
{
SessionReplicationContext ctx = getCurrentContext();
if (ctx != null)
{
ctx.webappCount--;
if (ctx.webappCount < 1)
{
// We've unwound any nested webapp calls, so we'll clean up and
// return the context to allow replication. If all cache activity
// is done as well, clear the ThreadLocal
ctx.outerRequest = null;
ctx.outerResponse = null;
replicationContext.set(null);
return ctx;
}
}
// A nested valve called us. Just return an empty context
return EMPTY;
}
public static void bindSession(ClusteredSession<? extends OutgoingDistributableSessionData> session, SnapshotManager manager)
{
SessionReplicationContext ctx = getCurrentContext();
if (ctx != null && ctx.webappCount > 0)
{
ctx.addReplicatableSession(session, manager);
}
/*else {
We are past the part of the request cycle where we
track sessions for replication
}*/
}
public static void sessionExpired(ClusteredSession<? extends OutgoingDistributableSessionData> session, String realId, SnapshotManager manager)
{
SessionReplicationContext ctx = getCurrentContext();
if (ctx != null && ctx.webappCount > 0)
{
ctx.sessionExpired(session, manager);
}
}
/**
* Does nothing
*
* @return <code>false</code>
*
* @deprecated Always returns false; will be removed in AS 6
*/
@Deprecated
public static boolean isSessionBoundAndExpired(String realId, SnapshotManager manager)
{
return false;
}
/**
* Does nothing
*
* @deprecated Does nothing; will be removed in AS 6
*/
@Deprecated
public static void startCacheActivity()
{
// no-op
}
/**
* Does nothing
*
* @deprecated Does nothing; will be removed in AS 6
*/
@Deprecated
public static void finishCacheActivity()
{
// no-op
}
/**
* Returns whether there is a SessionReplicationContext associated with
* the current thread.
*
* @return <code>true</code> if there is a context associated with the thread
*/
public static boolean isLocallyActive()
{
return getCurrentContext() != null;
}
public static Request getOriginalRequest()
{
SessionReplicationContext ctx = getCurrentContext();
return (ctx == null ? null : ctx.outerRequest);
}
public static Response getOriginalResponse()
{
SessionReplicationContext ctx = getCurrentContext();
return (ctx == null ? null : ctx.outerResponse);
}
private static SessionReplicationContext getCurrentContext()
{
return replicationContext.get();
}
private SessionReplicationContext(Request request, Response response)
{
this.outerRequest = request;
this.outerResponse = response;
}
private SessionReplicationContext() {}
/**
* Gets a Map<SnapshotManager, ClusteredSession> of sessions that were accessed
* during the course of a request. Will only be non-null if
* {@link #bindSession(ClusteredSession, SnapshotManager)} was called
* with more than one SnapshotManager (i.e the request crossed session
* contexts.)
*/
public Map<ClusteredSession<? extends OutgoingDistributableSessionData>, SnapshotManager> getCrossContextSessions()
{
return crossCtxSessions;
}
/**
* Gets the SnapshotManager that was passed to
* {@link #bindSession(ClusteredSession, SnapshotManager)} if and only
* if only one such SnapshotManager was passed. Returns <code>null</code>
* otherwise, in which case a cross-context request is a possibility,
* and {@link #getCrossContextSessions()} should be checked.
*/
public SnapshotManager getSoleSnapshotManager()
{
return soleManager;
}
/**
* Gets the ClusteredSession that was passed to
* {@link #bindSession(ClusteredSession, SnapshotManager)} if and only
* if only one SnapshotManager was passed. Returns <code>null</code>
* otherwise, in which case a cross-context request is a possibility,
* and {@link #getCrossContextSessions()} should be checked.
*/
public ClusteredSession<? extends OutgoingDistributableSessionData> getSoleSession()
{
return soleSession;
}
private void addReplicatableSession(ClusteredSession<? extends OutgoingDistributableSessionData> session, SnapshotManager mgr)
{
if (crossCtxSessions != null)
{
crossCtxSessions.put(session, mgr);
}
else if (soleManager == null)
{
// First one bound
soleManager = mgr;
soleSession = session;
}
else if (!mgr.equals(soleManager))
{
// We have a cross-context call; need a Map for the sessions
crossCtxSessions = new HashMap<ClusteredSession<? extends OutgoingDistributableSessionData>, SnapshotManager>();
crossCtxSessions.put(soleSession, soleManager);
crossCtxSessions.put(session, mgr);
soleManager = null;
soleSession = null;
}
else
{
soleSession = session;
}
}
private void sessionExpired(ClusteredSession<? extends OutgoingDistributableSessionData> session, SnapshotManager manager)
{
if (manager.equals(soleManager))
{
soleManager = null;
soleSession = null;
}
else if (crossCtxSessions != null)
{
crossCtxSessions.remove(session);
}
}
}