/*
* 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.as.undertow.session;
import java.util.HashMap;
import java.util.Map;
import io.undertow.server.HttpServerExchange;
import org.jboss.as.clustering.web.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 HttpServerExchange exchange;
/**
* 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(HttpServerExchange exchange, boolean startCacheActivity) {
SessionReplicationContext ctx = getCurrentContext();
if (ctx == null) {
ctx = new SessionReplicationContext(exchange);
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.exchange = 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);
}
}
/**
* 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 HttpServerExchange getCurrentExchange() {
SessionReplicationContext ctx = getCurrentContext();
return (ctx == null ? null : ctx.exchange);
}
private static SessionReplicationContext getCurrentContext() {
return replicationContext.get();
}
private SessionReplicationContext(HttpServerExchange exchange) {
this.exchange = exchange;
}
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);
}
}
}