/* * 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.text.ParseException; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.servlet.http.HttpSession; import javax.servlet.sip.SipApplicationSession; import javax.servlet.sip.SipSession; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.TransactionManager; import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.Host; import org.apache.catalina.LifecycleException; import org.apache.catalina.Session; import org.apache.catalina.Valve; import org.apache.catalina.core.ContainerBase; import org.jboss.cache.CacheException; import org.jboss.cache.aop.PojoCacheMBean; import org.jboss.logging.Logger; import org.jboss.metadata.WebMetaData; import org.jboss.mx.util.MBeanServerLocator; import org.jboss.web.tomcat.service.JBossWeb; import org.mobicents.servlet.sip.core.session.DistributableSipManager; import org.mobicents.servlet.sip.core.session.MobicentsSipApplicationSession; import org.mobicents.servlet.sip.core.session.MobicentsSipSession; import org.mobicents.servlet.sip.core.session.SessionManagerUtil; import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey; import org.mobicents.servlet.sip.core.session.SipManagerDelegate; import org.mobicents.servlet.sip.core.session.SipSessionKey; import org.mobicents.servlet.sip.message.SipFactoryImpl; import org.mobicents.servlet.sip.startup.SipContext; /** * Implementation of a converged clustered session manager for * catalina using JBossCache replication. * * Based on JbossCacheManager JBOSS AS 4.2.2 Tag * I was forced to copy over most of the code since some things that needed to be adapted * were private * * @author Ben Wang * @author Brian Stansberry * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ public class JBossCacheSipManager extends JBossCacheManager implements DistributableSipManager { /** * The descriptive information about this implementation. */ protected static final String info = "JBossCacheSipManager/1.0"; protected static Logger logger = Logger.getLogger(JBossCacheSipManager.class); private SipManagerDelegate sipManagerDelegate; /** * Informational name for this Catalina component */ static final String info_ = "JBossCacheSipManager/1.0"; // -- Class attributes --------------------------------- /** * The transaction manager. */ protected TransactionManager tm; /** * Proxy-object for the JBossCacheService */ private ConvergedJBossCacheService proxy_; /** * Id/timestamp of sessions in cache that we haven't loaded */ private Map unloadedSessions_ = new ConcurrentHashMap(); /** * Id/timestamp of sessions in cache that we haven't loaded */ private Map unloadedSipSessions_ = new ConcurrentHashMap(); /** * Id/timestamp of sessions in cache that we haven't loaded */ private Map unloadedSipApplicationSessions_ = new ConcurrentHashMap(); /** Our TreeCache's ObjectName */ private String cacheObjectNameString_ = JBossWeb.DEFAULT_CACHE_NAME; /** * If set to true, will add a JvmRouteFilter to the request. */ protected boolean useJK_ = false; /** Are we running embedded in JBoss? */ private boolean embedded_ = false; /** Our JMX Server */ private MBeanServer mserver_ = null; /** Our ClusteredSessionValve's snapshot mode. */ private String snapshotMode_ = null; /** Our ClusteredSessionValve's snapshot interval. */ private int snapshotInterval_ = 0; /** String form of invalidateSessionPolicy_ */ private String replTriggerString_ = null; /** String form of replGranularityString_ */ private String replGranularityString_ = null; /** * Whether we use batch mode replication for field level granularity. We * store this in a Boolean rather than a primitive so JBossCacheCluster can * determine if this was set via a <Manager> element. */ private Boolean replicationFieldBatchMode_; /** Class loader for this web app. */ private ClassLoader tcl_; /** * The snapshot manager we are using. */ private SnapshotManager snapshotManager_; /** * Whether we are doing trace level logging */ private boolean trace; private int maxUnreplicatedInterval_ = WebMetaData.DEFAULT_MAX_UNREPLICATED_INTERVAL; // ---------------------------------------------------------- Constructors public JBossCacheSipManager() { super(); } /** * Create a new JBossCacheManager using the given cache. For use in unit testing. * * @param pojoCache */ public JBossCacheSipManager(PojoCacheMBean pojoCache) { super(); this.proxy_ = new ConvergedJBossCacheService(pojoCache); } /** * Initializes this Manager when running in embedded mode. * <p> * <strong>NOTE:</strong> This method should not be called when running * unembedded. * </p> */ public void init(String name, WebMetaData webMetaData, boolean useJK, boolean useLocalCache) throws ClusteringNotSupportedException { replicationGranularity_ = webMetaData.getReplicationGranularity(); invalidateSessionPolicy_ = webMetaData.getInvalidateSessionPolicy(); sipManagerDelegate = new ClusteredSipManagerDelegate(replicationGranularity_); useLocalCache_ = useLocalCache; log_.info("init(): replicationGranularity_ is " + replicationGranularity_ + " and invalidateSessionPolicy is " + invalidateSessionPolicy_); try { // Give this manager a name objectName_ = new ObjectName( "jboss.web:service=ClusterManager,WebModule=" + name); } catch (Throwable e) { log_.error("Could not create ObjectName", e); throw new ClusteringNotSupportedException(e.toString()); } this.useJK_ = useJK; this.replicationFieldBatchMode_ = webMetaData .getReplicationFieldBatchMode() ? Boolean.TRUE : Boolean.FALSE; Integer maxUnrep = webMetaData.getMaxUnreplicatedInterval(); if (maxUnrep != null) { this.maxUnreplicatedInterval_ = maxUnrep.intValue(); } if (proxy_ == null) proxy_ = new ConvergedJBossCacheService(cacheObjectNameString_); // Confirm our replication granularity is compatible with the cache // Throws ISE if not validateFieldMarshalling(); embedded_ = true; } // ------------------------------------------------------------- Properties /** * Gets the <code>JBossCacheService</code> through which we interact with * the <code>TreeCache</code>. */ public JBossCacheService getCacheService() { return proxy_; } /** * Gets a String representation of the JMX <code>ObjectName</code> under * which our <code>TreeCache</code> is registered. */ public String getCacheObjectNameString() { return cacheObjectNameString_; } /** * Sets the JMX <code>ObjectName</code> under which our * <code>TreeCache</code> is registered. */ public void setCacheObjectNameString(String treeCacheObjectName) { this.cacheObjectNameString_ = treeCacheObjectName; } /** * Gets when sessions are replicated to the other nodes. The default value, * "instant", synchronously replicates changes to the other nodes. In this * case, the "SnapshotInterval" attribute is not used. The "interval" mode, * in association with the "SnapshotInterval" attribute, indicates that * Tomcat will only replicate modified sessions every "SnapshotInterval" * miliseconds at most. * * @see #getSnapshotInterval() */ public String getSnapshotMode() { return snapshotMode_; } /** * Sets when sessions are replicated to the other nodes. Valid values are: * <ul> * <li>instant</li> * <li>interval</li> * </ul> */ public void setSnapshotMode(String snapshotMode) { this.snapshotMode_ = snapshotMode; } /** * Gets how often session changes should be replicated to other nodes. Only * relevant if property {@link #getSnapshotMode() snapshotMode} is set to * <code>interval</code>. * * @return the number of milliseconds between session replications. */ public int getSnapshotInterval() { return snapshotInterval_; } /** * Sets how often session changes should be replicated to other nodes. * * @param snapshotInterval * the number of milliseconds between session replications. */ public void setSnapshotInterval(int snapshotInterval) { this.snapshotInterval_ = snapshotInterval; } /** * Gets whether the <code>Engine</code> in which we are running uses * <code>mod_jk</code>. */ public boolean getUseJK() { return useJK_; } /** * Sets whether the <code>Engine</code> in which we are running uses * <code>mod_jk</code>. */ public void setUseJK(boolean useJK) { this.useJK_ = useJK; } /** * Returns the replication granularity expressed as an int. * * @see WebMetaData#REPLICATION_GRANULARITY_ATTRIBUTE * @see WebMetaData#REPLICATION_GRANULARITY_FIELD * @see WebMetaData#REPLICATION_GRANULARITY_SESSION */ public int getReplicationGranularity() { return replicationGranularity_; } /** * Gets the granularity of session data replicated across the cluster; i.e. * whether the entire session should be replicated when replication is * triggered, only modified attributes, or only modified fields of * attributes. */ public String getReplicationGranularityString() { // Only lazy-set this if we are started; // otherwise screws up standalone TC integration!! if (started_ && this.replGranularityString_ == null) { switch (this.replicationGranularity_) { case WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE: this.replGranularityString_ = "ATTRIBUTE"; break; case WebMetaData.REPLICATION_GRANULARITY_SESSION: this.replGranularityString_ = "SESSION"; break; case WebMetaData.REPLICATION_GRANULARITY_FIELD: this.replGranularityString_ = "FIELD"; } } return replGranularityString_; } /** * Sets the granularity of session data replicated across the cluster. Valid * values are: * <ul> * <li>SESSION</li> * <li>ATTRIBUTE</li> * <li>FIELD</li> * </ul> */ public void setReplicationGranularityString(String granularity) { this.replGranularityString_ = granularity; } /** * Gets the type of operations on a <code>HttpSession</code> that trigger * replication. */ public String getReplicationTriggerString() { // Only lazy-set this if we are started; // otherwise screws up standalone TC integration!! if (started_ && this.replTriggerString_ == null) { switch (this.invalidateSessionPolicy_) { case WebMetaData.SESSION_INVALIDATE_SET: this.replTriggerString_ = "SET"; break; case WebMetaData.SESSION_INVALIDATE_SET_AND_GET: this.replTriggerString_ = "SET_AND_GET"; break; case WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET: this.replTriggerString_ = "SET_AND_NON_PRIMITIVE_GET"; } } return this.replTriggerString_; } /** * Sets the type of operations on a <code>HttpSession</code> that trigger * replication. Valid values are: * <ul> * <li>SET_AND_GET</li> * <li>SET_AND_NON_PRIMITIVE_GET</li> * <li>SET</li> * </ul> */ public void setReplicationTriggerString(String trigger) { this.replTriggerString_ = trigger; } /** * Gets whether, if replication granularity is set to <code>FIELD</code>, * replication should be done in batch mode. Ignored if field-level * granularity is not used. */ public Boolean isReplicationFieldBatchMode() { return replicationFieldBatchMode_; } /** * Sets whether, if replication granularity is set to <code>FIELD</code>, * replication should be done in batch mode. Ignored if field-level * granularity is not used. */ public void setReplicationFieldBatchMode(boolean replicationFieldBatchMode) { this.replicationFieldBatchMode_ = Boolean .valueOf(replicationFieldBatchMode); } public void setUseLocalCache(boolean useLocalCache) { this.useLocalCache_ = useLocalCache; } public int getMaxUnreplicatedInterval() { return maxUnreplicatedInterval_; } public void setMaxUnreplicatedInterval(int maxUnreplicatedInterval) { this.maxUnreplicatedInterval_ = maxUnreplicatedInterval; } // JBossCacheManagerMBean-methods ------------------------------------- public void expireSession(String sessionId) { Session session = findSession(sessionId); if (session != null) session.expire(); } public String getLastAccessedTime(String sessionId) { Session session = findSession(sessionId); if (session == null) { log_.debug("getLastAccessedTime(): Session " + sessionId + " not found"); return ""; } return new Date(session.getLastAccessedTime()).toString(); } public Object getSessionAttribute(String sessionId, String key) { ClusteredSession session = (ClusteredSession) findSession(sessionId); return (session == null) ? null : session.getAttribute(key); } public String getSessionAttributeString(String sessionId, String key) { Object attr = getSessionAttribute(sessionId, key); return (attr == null) ? null : attr.toString(); } public String listLocalSessionIds() { return reportSessionIds(sessions_.keySet()); } public String listSessionIds() { Set ids = new HashSet(sessions_.keySet()); ids.addAll(unloadedSessions_.keySet()); return reportSessionIds(ids); } private String reportSessionIds(Set ids) { StringBuffer sb = new StringBuffer(); boolean added = false; for (Iterator it = ids.iterator(); it.hasNext();) { if (added) { sb.append(','); } else { added = true; } sb.append(it.next()); } return sb.toString(); } // Manager-methods ------------------------------------- /** * Start this Manager * * @throws org.apache.catalina.LifecycleException * */ public void start() throws LifecycleException { if (embedded_) { startEmbedded(); } else { startUnembedded(); } } public void stop() throws LifecycleException { if (!started_) { throw new IllegalStateException("Manager not started"); } log_.debug("Stopping"); resetStats(); // Notify our interested LifecycleListeners lifecycle_.fireLifecycleEvent(BEFORE_STOP_EVENT, this); clearSessions(); // Don't leak the classloader tcl_ = null; proxy_.stop(); tm = null; snapshotManager_.stop(); started_ = false; // Notify our interested LifecycleListeners lifecycle_.fireLifecycleEvent(AFTER_STOP_EVENT, this); try { unregisterMBeans(); } catch (Exception e) { log_.error("Could not unregister ManagerMBean from MBeanServer", e); } } /** * Clear the underlying cache store and also pojo that has the observers. */ protected void clearSessions() { // First, the sessions we have actively loaded ClusteredSession[] sessions = findLocalSessions(); for (int i = 0; i < sessions.length; i++) { ClusteredSession ses = sessions[i]; if (log_.isDebugEnabled()) { log_ .debug("clearSessions(): clear session by expiring: " + ses); } boolean notify = true; boolean localCall = true; boolean localOnly = true; try { ses.expire(notify, localCall, localOnly); } catch (Throwable t) { log_.warn("clearSessions(): Caught exception expiring session " + ses.getIdInternal(), t); } finally { // Guard against leaking memory if anything is holding a // ref to the session by clearing its internal state ses.recycle(); } } // Next, the local copy of the distributed cache Map unloaded = new HashMap(unloadedSessions_); Set keys = unloaded.keySet(); for (Iterator it = keys.iterator(); it.hasNext();) { String realId = (String) it.next(); proxy_.removeSessionLocal(realId); unloadedSessions_.remove(realId); } } /** * Clear the underlying cache store and also pojo that has the observers. */ protected void clearSipApplicationSessions() { // First, the sessions we have actively loaded ClusteredSession[] sessions = findLocalSessions(); for (int i = 0; i < sessions.length; i++) { ClusteredSession ses = sessions[i]; if (log_.isDebugEnabled()) { log_ .debug("clearSessions(): clear session by expiring: " + ses); } boolean notify = true; boolean localCall = true; boolean localOnly = true; try { ses.expire(notify, localCall, localOnly); } catch (Throwable t) { log_.warn("clearSessions(): Caught exception expiring session " + ses.getIdInternal(), t); } finally { // Guard against leaking memory if anything is holding a // ref to the session by clearing its internal state ses.recycle(); } } // Next, the local copy of the distributed cache Map unloaded = new HashMap(unloadedSipSessions_); Set keys = unloaded.keySet(); for (Iterator it = keys.iterator(); it.hasNext();) { String realId = (String) it.next(); proxy_.removeSipApplicationSessionLocal(realId); unloadedSipSessions_.remove(realId); } } /** * Create a new session with a generated id. */ public Session createSession() { return createSession(null); } /** * Create a new session. * * @param sessionId * the id to use, or <code>null</code> if we should generate a * new id * * @return the session * * @throws IllegalStateException * if the current number of active sessions exceeds the maximum * number allowed */ public Session createSession(String sessionId) { // We check here for maxActive instead of in add(). add() gets called // when we load an already existing session from the distributed cache // (e.g. in a failover) and we don't want to fail in that situation. // maxActive_ -1 is unlimited if (maxActive_ != -1 && activeCounter_ >= maxActive_) { // Exceeds limit. We need to reject it. rejectedCounter_++; // Catalina api does not specify what happens // but we will throw a runtime exception for now. String msgEnd = (sessionId == null) ? "" : " id " + sessionId; throw new IllegalStateException( "JBossCacheManager.add(): number of " + "active sessions exceeds the maximum limit: " + maxActive_ + " when trying to add session" + msgEnd); } ClusteredSession session = createEmptyClusteredSession(); session.setNew(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval_); session.setValid(true); if (sessionId == null) { sessionId = this.getNextId(); // We are using mod_jk for load balancing. Append the JvmRoute. if (useJK_) { if (log_.isDebugEnabled()) { log_ .debug("createSession(): useJK is true. Will append JvmRoute: " + this.getJvmRoute()); } sessionId += "." + this.getJvmRoute(); } } session.setId(sessionId); // Setting the id leads to a call to add() if (log_.isDebugEnabled()) { log_.debug("Created a ClusteredSession with id: " + sessionId); } createdCounter_++; // Add this session to the set of those potentially needing replication ConvergedSessionReplicationContext.bindSession(session, snapshotManager_); return session; } public boolean storeSession(Session baseSession) { boolean stored = false; if (baseSession != null && started_) { ClusteredSession session = (ClusteredSession) baseSession; synchronized (session) { if (logger.isDebugEnabled()) { log_.debug("check to see if needs to store and replicate " + "session with id " + session.getIdInternal()); } if (session.isValid() && (session.isSessionDirty() || session .getExceedsMaxUnreplicatedInterval())) { String realId = session.getRealId(); // Notify all session attributes that they get serialized // (SRV 7.7.2) long begin = System.currentTimeMillis(); session.passivate(); long elapsed = System.currentTimeMillis() - begin; stats_.updatePassivationStats(realId, elapsed); // Do the actual replication begin = System.currentTimeMillis(); processSessionRepl(session); elapsed = System.currentTimeMillis() - begin; stored = true; stats_.updateReplicationStats(realId, elapsed); } } } return stored; } public boolean storeSipSession(SipSession baseSession) { boolean stored = false; if (baseSession != null && started_) { ClusteredSipSession session = (ClusteredSipSession) baseSession; synchronized (session) { if (logger.isDebugEnabled()) { logger.debug("check to see if needs to store and replicate " + "session with id " + session.getId()); } if (session.isValid() && (session.isSessionDirty() || session .getExceedsMaxUnreplicatedInterval())) { if(logger.isInfoEnabled()) { logger.info("replicating following sip session " + session.getId()); } String realId = session.getId(); // Notify all session attributes that they get serialized // (SRV 7.7.2) long begin = System.currentTimeMillis(); session.passivate(); long elapsed = System.currentTimeMillis() - begin; stats_.updatePassivationStats(realId, elapsed); // Do the actual replication begin = System.currentTimeMillis(); processSipSessionRepl(session); elapsed = System.currentTimeMillis() - begin; stored = true; stats_.updateReplicationStats(realId, elapsed); } } } return stored; } public boolean storeSipApplicationSession(SipApplicationSession baseSession) { boolean stored = false; if (baseSession != null && started_) { ClusteredSipApplicationSession session = (ClusteredSipApplicationSession) baseSession; synchronized (session) { if (logger.isDebugEnabled()) { log_.debug("check to see if needs to store and replicate " + "session with id " + session.getId()); } if (session.isValid() && (session.isSessionDirty() || session .getExceedsMaxUnreplicatedInterval())) { if(logger.isInfoEnabled()) { logger.info("replicating following sip application session " + session.getId()); } String realId = session.getId(); // Notify all session attributes that they get serialized // (SRV 7.7.2) long begin = System.currentTimeMillis(); session.passivate(); long elapsed = System.currentTimeMillis() - begin; stats_.updatePassivationStats(realId, elapsed); // Do the actual replication begin = System.currentTimeMillis(); processSipApplicationSessionRepl(session); elapsed = System.currentTimeMillis() - begin; stored = true; stats_.updateReplicationStats(realId, elapsed); } } } return stored; } public void add(Session session) { if (session == null) return; if (!(session instanceof ClusteredSession)) { throw new IllegalArgumentException( "You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } // add((ClusteredSession) session, true); add((ClusteredSession) session, false); } public void add(SipSession session) { if (session == null) return; if (!(session instanceof ClusteredSipSession)) { throw new IllegalArgumentException( "You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } // add((ClusteredSession) session, true); add((ClusteredSipSession) session, false); } public void add(SipApplicationSession session) { if (session == null) return; if (!(session instanceof ClusteredSipApplicationSession)) { throw new IllegalArgumentException( "You can only add instances of " + "type ClusteredSession to this Manager. Session class name: " + session.getClass().getName()); } // add((ClusteredSession) session, true); add((ClusteredSipApplicationSession) session, false); } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session * the session. Cannot be <code>null</code>. * @param replicate * whether the session should be replicated * * @throws NullPointerException * if <code>session</code> is <code>null</code>. */ private void add(ClusteredSession session, boolean replicate) { if (!session.isValid()) { log_.error("Cannot add session with id=" + session.getIdInternal() + " because it is invalid"); return; } String realId = session.getRealId(); Object existing = sessions_.put(realId, session); unloadedSessions_.remove(realId); if (!session.equals(existing)) { if (replicate) { storeSession(session); } activeCounter_++; if (activeCounter_ > maxActiveCounter_) maxActiveCounter_++; if (log_.isDebugEnabled()) { log_.debug("Session with id=" + session.getIdInternal() + " added. " + "Current active sessions " + activeCounter_); } } } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session * the session. Cannot be <code>null</code>. * @param replicate * whether the session should be replicated * * @throws NullPointerException * if <code>session</code> is <code>null</code>. */ private void add(ClusteredSipSession session, boolean replicate) { if (!session.isValid()) { log_.error("Cannot add session with id=" + session.getId() + " because it is invalid"); return; } SipSessionKey key = session.getKey(); Object existing = ((ClusteredSipManagerDelegate)sipManagerDelegate).putSipSession(key, session); unloadedSipSessions_.remove(key.toString()); if (!session.equals(existing)) { if (replicate) { storeSipSession(session); } activeCounter_++; if (activeCounter_ > maxActiveCounter_) maxActiveCounter_++; if (log_.isDebugEnabled()) { log_.debug("Session with id=" + session.getId() + " added. " + "Current active sessions " + activeCounter_); } } } /** * Adds the given session to the collection of those being managed by this * Manager. * * @param session * the session. Cannot be <code>null</code>. * @param replicate * whether the session should be replicated * * @throws NullPointerException * if <code>session</code> is <code>null</code>. */ private void add(ClusteredSipApplicationSession session, boolean replicate) { if (!session.isValid()) { log_.error("Cannot add session with id=" + session.getId() + " because it is invalid"); return; } SipApplicationSessionKey key = session.getKey(); Object existing = ((ClusteredSipManagerDelegate)sipManagerDelegate).putSipApplicationSession(key, session); unloadedSipApplicationSessions_.remove(key.toString()); if (!session.equals(existing)) { if (replicate) { storeSipApplicationSession(session); } activeCounter_++; if (activeCounter_ > maxActiveCounter_) maxActiveCounter_++; if (log_.isDebugEnabled()) { log_.debug("Session with id=" + session.getId() + " added. " + "Current active sessions " + activeCounter_); } } } /** * Attempts to find the session in the collection of those being managed * locally, and if not found there, in the distributed cache of sessions. * <p> * If a session is found in the distributed cache, it is added to the * collection of those being managed locally. * </p> * * @param id * the session id, which may include an appended jvmRoute * * @return the session, or <code>null</code> if no such session could be * found */ public Session findSession(String id) { String realId = getRealId(id); // Find it from the local store first ClusteredSession session = findLocalSession(realId); // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy // replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !ConvergedSessionReplicationContext .isSessionBoundAndExpired(realId, snapshotManager_)) { if (logger.isDebugEnabled()) log_.debug("Checking for session " + realId + " in the distributed cache"); session = loadSession(realId); if (session != null) { add(session); // TODO should we advise of a new session? // tellNew(); } } else if (session != null && session.isOutdated()) { if (logger.isDebugEnabled()) log_.debug("Updating session " + realId + " from the distributed cache"); // Need to update it from the cache loadSession(realId); } if (session != null) { // Add this session to the set of those potentially needing // replication ConvergedSessionReplicationContext.bindSession(session, snapshotManager_); } return session; } /** * Return the sessions. Note that this will return not only the local * in-memory sessions, but also any sessions that are in the distributed * cache but have not previously been accessed on this server. Invoking this * method will bring all such sessions into local memory and can potentially * be quite expensive. * * <p> * Note also that when sessions are loaded from the distributed cache, no * check is made as to whether the number of local sessions will thereafter * exceed the maximum number allowed on this server. * </p> * * @return an array of all the sessions */ public Session[] findSessions() { // Need to load all the unloaded sessions if (unloadedSessions_.size() > 0) { // Make a thread-safe copy of the new id list to work with Set ids = new HashSet(unloadedSessions_.keySet()); if (log_.isDebugEnabled()) { log_ .debug("findSessions: loading sessions from distributed cache: " + ids); } for (Iterator it = ids.iterator(); it.hasNext();) { loadSession((String) it.next()); } } // All sessions are now "local" so just return the local sessions return findLocalSessions(); } /** * Returns all the sessions that are being actively managed by this manager. * This includes those that were created on this server, those that were * brought into local management by a call to * {@link #findLocalSession(String)} as well as all sessions brought into * local management by a call to {@link #findSessions()}. */ public ClusteredSession[] findLocalSessions() { Collection coll = sessions_.values(); ClusteredSession[] sess = new ClusteredSession[coll.size()]; sess = (ClusteredSession[]) coll.toArray(sess); return sess; } /** * Returns the given session if it is being actively managed by this * manager. An actively managed session is on that was either created on * this server, brought into local management by a call to * {@link #findLocalSession(String)} or brought into local management by a * call to {@link #findSessions()}. * * @param realId * the session id, with any trailing jvmRoute removed. * * @see #getRealId(String) */ public ClusteredSession findLocalSession(String realId) { return (ClusteredSession) sessions_.get(realId); } /** * Returns the given sip session if it is being actively managed by this * manager. An actively managed sip session is on that was either created on * this server, brought into local management by a call to * {@link #findLocalSipSession(String)} or brought into local management by a * call to {@link #findSipSessions()}. * * @param key * the session key, with any trailing jvmRoute removed. */ public ClusteredSipSession findLocalSipSession(final SipSessionKey key, final boolean create, final MobicentsSipApplicationSession sipApplicationSessionImpl) { return (ClusteredSipSession) sipManagerDelegate.getSipSession(key, create, sipManagerDelegate.getSipFactoryImpl(), sipApplicationSessionImpl); } /** * Returns the given sip application session if it is being actively managed by this * manager. An actively managed sip application session is on that was either created on * this server, brought into local management by a call to * {@link #findLocalSipApplicationSession(String)} or brought into local management by a * call to {@link #findSipApplicationSessions()}. * * @param key * the session key, with any trailing jvmRoute removed. */ public ClusteredSipApplicationSession findLocalSipApplicationSession(final SipApplicationSessionKey key, final boolean create) { return (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, create); } /** * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from the distributed cache, both on * this server and on all other server to which this one replicates. */ public void remove(Session session) { ClusteredSession clusterSess = (ClusteredSession) session; synchronized (clusterSess) { String realId = clusterSess.getRealId(); if (realId == null) return; if (log_.isDebugEnabled()) { log_.debug("Removing session from store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startCacheActivity(); clusterSess.removeMyself(); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sessionExpired(clusterSess, realId, snapshotManager_); sessions_.remove(realId); stats_.removeStats(realId); activeCounter_--; } } } /** * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from this server's copy of the * distributed cache (but does not remove it from other servers' distributed * cache). */ public void removeLocal(Session session) { ClusteredSession clusterSess = (ClusteredSession) session; synchronized (clusterSess) { String realId = clusterSess.getRealId(); if (realId == null) return; if (log_.isDebugEnabled()) { log_.debug("Removing session from local store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startCacheActivity(); clusterSess.removeMyselfLocal(); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sessionExpired(clusterSess, realId, snapshotManager_); sessions_.remove(realId); stats_.removeStats(realId); // Update counters. // It's a bit ad-hoc to do it here. But since we currently call // this when session expires ... expiredCounter_++; activeCounter_--; } } } /** * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from this server's copy of the * distributed cache (but does not remove it from other servers' distributed * cache). */ public void removeLocal(SipSession session) { ClusteredSipSession clusterSess = (ClusteredSipSession) session; synchronized (clusterSess) { String realId = clusterSess.getId(); if (realId == null) return; if (log_.isDebugEnabled()) { log_.debug("Removing session from local store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startSipCacheActivity(); clusterSess.removeMyselfLocal(); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipSessionExpired( clusterSess, realId, snapshotManager_); sipManagerDelegate.removeSipSession(clusterSess.getKey()); stats_.removeStats(realId); // Update counters. // It's a bit ad-hoc to do it here. But since we currently call // this when session expires ... expiredCounter_++; activeCounter_--; } } } /** * Removes the session from this Manager's collection of actively managed * sessions. Also removes the session from this server's copy of the * distributed cache (but does not remove it from other servers' distributed * cache). */ public void removeLocal(SipApplicationSession session) { ClusteredSipApplicationSession clusterSess = (ClusteredSipApplicationSession) session; synchronized (clusterSess) { String realId = clusterSess.getId(); if (realId == null) return; if (log_.isDebugEnabled()) { log_.debug("Removing session from local store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startSipCacheActivity(); clusterSess.removeMyselfLocal(); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext .sipApplicationSessionExpired(clusterSess, realId, snapshotManager_); sipManagerDelegate.removeSipApplicationSession(clusterSess.getKey()); stats_.removeStats(realId); // Update counters. // It's a bit ad-hoc to do it here. But since we currently call // this when session expires ... expiredCounter_++; activeCounter_--; } } } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session will * be created and added to the collection of those sessions under local * management. * * @param realId * id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store * * TODO refactor this into 2 overloaded methods -- one that takes a * ClusteredSession and populates it and one that takes an id, * creates the session and calls the first */ protected ClusteredSession loadSession(String realId) { if (realId == null) { return null; } long begin = System.currentTimeMillis(); boolean mustAdd = false; ClusteredSession session = (ClusteredSession) sessions_.get(realId); if (session == null) { // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; session = createEmptyClusteredSession(); } synchronized (session) { boolean doTx = false; try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Ignore cache notifications we may generate for this // session if data gravitation occurs. ConvergedSessionReplicationContext.startCacheActivity(); session = proxy_.loadSession(realId, session); } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Problem rolling back session mgmt transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("Failed to load session " + realId, ex); } finally { try { if (doTx) endTransaction(realId); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); } } if (session != null) { // Need to initialize. session.initAfterLoad(this); if (mustAdd) add(session, false); // don't replicate long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(realId, elapsed); if (log_.isDebugEnabled()) { log_.debug("loadSession(): id= " + realId + ", session=" + session); } } else if (log_.isDebugEnabled()) { log_.debug("loadSession(): session " + realId + " not found in distributed cache"); } } return session; } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session will * be created and added to the collection of those sessions under local * management. * * @param realId * id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store * * TODO refactor this into 2 overloaded methods -- one that takes a * ClusteredSession and populates it and one that takes an id, * creates the session and calls the first */ protected ClusteredSipSession loadSipSession(final SipSessionKey key, final boolean create, final SipFactoryImpl sipFactoryImpl, final MobicentsSipApplicationSession sipApplicationSessionImpl) { if (key == null) { return null; } if(logger.isDebugEnabled()) { logger.debug("load sip session " + key + ", create = " + create + " sip app session = "+ sipApplicationSessionImpl); } SipFactoryImpl sipFactory = sipFactoryImpl; if(sipFactory == null) { sipFactory = this.getSipFactoryImpl(); } long begin = System.currentTimeMillis(); boolean mustAdd = false; ClusteredSipSession session = (ClusteredSipSession) sipManagerDelegate.getSipSession(key, create, sipFactory, sipApplicationSessionImpl); boolean doTx = false; try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Ignore cache notifications we may generate for this // session if data gravitation occurs. ConvergedSessionReplicationContext.startSipCacheActivity(); Object sessionData = proxy_.getSipSessionData(sipApplicationSessionImpl.getId(), key.toString()); if(sessionData != null) { if (session == null && sipApplicationSessionImpl != null) { // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; session = (ClusteredSipSession) sipManagerDelegate.getSipSession(key, true, sipFactory, sipApplicationSessionImpl); } session = proxy_.loadSipSession(sipApplicationSessionImpl.getId(), session, sessionData); } } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Problem rolling back session mgmt transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("Failed to load session " + key.toString(), ex); } finally { try { if (doTx) endTransaction(key.toString()); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); } } if (session != null) { // Need to initialize. session.initAfterLoad(this); if (mustAdd) add(session, false); // don't replicate long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(key.toString(), elapsed); if (log_.isDebugEnabled()) { log_.debug("loadSession(): id= " + key.toString() + ", session=" + session); } return session; } else if (log_.isDebugEnabled()) { log_.debug("loadSession(): session " + key.toString() + " not found in distributed cache"); } if(session != null) { ConvergedSessionReplicationContext.bindSipSession(session, snapshotManager_); } return session; } /** * Loads a session from the distributed store. If an existing session with * the id is already under local management, that session's internal state * will be updated from the distributed store. Otherwise a new session will * be created and added to the collection of those sessions under local * management. * * @param realId * id of the session-id with any jvmRoute removed * * @return the session or <code>null</code> if the session cannot be found * in the distributed store * * TODO refactor this into 2 overloaded methods -- one that takes a * ClusteredSession and populates it and one that takes an id, * creates the session and calls the first */ protected ClusteredSipApplicationSession loadSipApplicationSession(final SipApplicationSessionKey key, final boolean create) { if (key == null) { return null; } long begin = System.currentTimeMillis(); boolean mustAdd = false; ClusteredSipApplicationSession session = (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, create); boolean doTx = false; try { // We need transaction so any data gravitation replication // is sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Ignore cache notifications we may generate for this // session if data gravitation occurs. ConvergedSessionReplicationContext.startSipCacheActivity(); Object sessionData = proxy_.getSipApplicationSessionData(key.toString()); if(sessionData != null) { if (session == null) { // This is either the first time we've seen this session on this // server, or we previously expired it and have since gotten // a replication message from another server mustAdd = true; session = (ClusteredSipApplicationSession) sipManagerDelegate.getSipApplicationSession(key, true); } session = proxy_.loadSipApplicationSession(session, sessionData); } } catch (Exception ex) { try { // if(doTx) // Let's set it no matter what. tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Problem rolling back session mgmt transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException("Failed to load session " + key, ex); } finally { try { if (doTx) endTransaction(key.toString()); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); } } if (session != null) { // Need to initialize. session.initAfterLoad(this); if (mustAdd) add(session, false); // don't replicate long elapsed = System.currentTimeMillis() - begin; stats_.updateLoadStats(key.toString(), elapsed); if (log_.isDebugEnabled()) { log_.debug("loadSession(): id= " + key.toString() + ", session=" + session); } return session; } else if (log_.isDebugEnabled()) { log_.debug("loadSession(): session " + key.toString() + " not found in distributed cache"); } ConvergedSessionReplicationContext.bindSipApplicationSession(session, snapshotManager_); return session; } /** * Places the current session contents in the distributed cache and * replicates them to the cluster * * @param session * the session. Cannot be <code>null</code>. */ protected void processSessionRepl(ClusteredSession session) { // If we are using SESSION granularity, we don't want to initiate a TX // for a single put boolean notSession = (replicationGranularity_ != WebMetaData.REPLICATION_GRANULARITY_SESSION); boolean doTx = false; try { // We need transaction so all the replication are sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (notSession && tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Tell the proxy to ignore cache notifications we are about // to generate for this session. We have to do this // at this level because we don't want to resume handling // notifications until any compensating changes resulting // from a tx rollback are done. ConvergedSessionReplicationContext.startCacheActivity(); session.processSessionRepl(); } catch (Exception ex) { if (log_.isDebugEnabled()) log_.debug("processSessionRepl(): failed with exception", ex); try { // if(doTx) // Let's setRollbackOnly no matter what. // (except if there's no tx due to SESSION (JBAS-3840)) if (notSession) tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException( "JBossCacheManager.processSessionRepl(): " + "failed to replicate session.", ex); } finally { try { if (doTx) endTransaction(session.getId()); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); } } } /** * Places the current session contents in the distributed cache and * replicates them to the cluster * * @param session * the session. Cannot be <code>null</code>. */ protected void processSipSessionRepl(ClusteredSipSession session) { // If we are using SESSION granularity, we don't want to initiate a TX // for a single put boolean notSession = (replicationGranularity_ != WebMetaData.REPLICATION_GRANULARITY_SESSION); boolean doTx = false; try { // We need transaction so all the replication are sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (notSession && tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Tell the proxy to ignore cache notifications we are about // to generate for this session. We have to do this // at this level because we don't want to resume handling // notifications until any compensating changes resulting // from a tx rollback are done. ConvergedSessionReplicationContext.startSipCacheActivity(); session.processSessionRepl(); } catch (Exception ex) { if (log_.isDebugEnabled()) log_.debug("processSessionRepl(): failed with exception", ex); try { // if(doTx) // Let's setRollbackOnly no matter what. // (except if there's no tx due to SESSION (JBAS-3840)) if (notSession) tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException( "JBossCacheManager.processSessionRepl(): " + "failed to replicate session.", ex); } finally { try { if (doTx) endTransaction(session.getId()); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); } } } /** * Places the current session contents in the distributed cache and * replicates them to the cluster * * @param session * the session. Cannot be <code>null</code>. */ protected void processSipApplicationSessionRepl(ClusteredSipApplicationSession session) { // If we are using SESSION granularity, we don't want to initiate a TX // for a single put boolean notSession = (replicationGranularity_ != WebMetaData.REPLICATION_GRANULARITY_SESSION); boolean doTx = false; try { // We need transaction so all the replication are sent in batch. // Don't do anything if there is already transaction context // associated with this thread. if (notSession && tm.getTransaction() == null) doTx = true; if (doTx) tm.begin(); // Tell the proxy to ignore cache notifications we are about // to generate for this session. We have to do this // at this level because we don't want to resume handling // notifications until any compensating changes resulting // from a tx rollback are done. ConvergedSessionReplicationContext.startSipCacheActivity(); session.processSessionRepl(); } catch (Exception ex) { if (log_.isDebugEnabled()) log_.debug("processSessionRepl(): failed with exception", ex); try { // if(doTx) // Let's setRollbackOnly no matter what. // (except if there's no tx due to SESSION (JBAS-3840)) if (notSession) tm.setRollbackOnly(); } catch (Exception exn) { log_.error("Caught exception rolling back transaction", exn); } // We will need to alert Tomcat of this exception. if (ex instanceof RuntimeException) throw (RuntimeException) ex; throw new RuntimeException( "JBossCacheManager.processSessionRepl(): " + "failed to replicate session.", ex); } finally { try { if (doTx) endTransaction(session.getId()); } finally { ConvergedSessionReplicationContext.finishCacheActivity(); } } } protected void endTransaction(String id) { if (tm == null) { log_.warn("JBossCacheManager.endTransaction(): tm is null for id: " + id); return; } try { if (tm.getTransaction().getStatus() != Status.STATUS_MARKED_ROLLBACK) { tm.commit(); } else { log_ .info("JBossCacheManager.endTransaction(): rolling back tx for id: " + id); tm.rollback(); } } catch (RollbackException re) { // Do nothing here since cache may rollback automatically. log_ .warn("JBossCacheManager.endTransaction(): rolling back transaction with exception: " + re); } catch (Exception e) { throw new RuntimeException( "JBossCacheManager.endTransaction(): Exception for id: " + id, e); } } /** * Gets the classloader of the webapp we are managing. */ protected ClassLoader getWebappClassLoader() { return tcl_; } /** * Overrides the superclass version to add a check as to whether we should * do trace level logging. */ @Override public void backgroundProcess() { trace = log_.isTraceEnabled(); // Called from Catalina StandardEngine for every 60 seconds. long start = System.currentTimeMillis(); processExpires(); long elapsed = System.currentTimeMillis() - start; processingTime_ += elapsed; } /** * Goes through all sessions and look if they have expired. Note this * overrides the method in JBossManager. */ protected void processExpires() { if (maxInactiveInterval_ < 0) { return; } if (log_.isDebugEnabled()) { log_.debug("Looking for sessions that have expired ..."); } try { // First, handle the sessions we are actively managing Session sessions[] = findLocalSessions(); for (int i = 0; i < sessions.length; ++i) { try { ClusteredSession session = (ClusteredSession) sessions[i]; if (session == null) { log_ .warn("processExpires(): processing null session at index " + i); continue; } // JBAS-2403. Check for outdated sessions where we think // the local copy has timed out. If found, refresh the // session from the cache in case that might change the // timeout if (session.isOutdated() && !(session.isValid(false))) { // JBAS-2792 don't assign the result of loadSession to // session // just update the object from the cache or fall through // if // the session has been removed from the cache loadSession(session.getRealId()); } // Do a normal invalidation check that will expire any // sessions that have timed out // DON'T SYNCHRONIZE on session here -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! if (!session.isValid()) continue; } catch (Exception ex) { log_.error("processExpires(): failed expiring " + sessions[i].getIdInternal() + " with exception: " + ex, ex); } } // Next, handle any unloaded sessions that are stale long now = System.currentTimeMillis(); Map unloaded = new HashMap(unloadedSessions_); Set entries = unloaded.entrySet(); // We may have not gotten replication of a timestamp for requests // that occurred w/in maxUnreplicatedInterval_ of the previous // request. So we add a grace period to avoid flushing a session // early // and permanently losing part of its node structure in JBoss Cache. long maxUnrep = maxUnreplicatedInterval_ < 0 ? 60 : maxUnreplicatedInterval_; long maxUnused = maxInactiveInterval_ + maxUnrep; for (Iterator it = entries.iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); OwnedSessionUpdate osu = (OwnedSessionUpdate) entry.getValue(); int elapsed = (int) ((now - osu.updateTime) / 1000L); if (elapsed >= maxUnused) { String realId = (String) entry.getKey(); try { proxy_.removeSessionLocal(realId, osu.owner); unloadedSessions_.remove(realId); } catch (Exception ex) { log_ .error( "processExpire(): failed removing unloaded session " + realId + " with exception: " + ex, ex); } } } } catch (Exception ex) { log_.error("processExpires: failed with exception: " + ex, ex); } } public void processRemoteAttributeRemoval(String realId, String attrKey) { ClusteredSession session = findLocalSession(realId); if (session != null) { boolean localCall = false; // call is due to remote event boolean localOnly = true; // don't call back into cache boolean notify = false; // SRV.10.7 gives us leeway // not to notify listeners, // which is safer // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); synchronized (session) { session.removeAttributeInternal(attrKey, localCall, localOnly, notify); } if (logger.isDebugEnabled()) log_ .debug("processRemoteAttributeRemoval: removed attribute " + attrKey + " from " + realId); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } } } public void processRemoteSipApplicationSessionAttributeRemoval(String realId, String attrKey) { SipApplicationSessionKey sipApplicationSessionKey; try { sipApplicationSessionKey = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip application session key " + realId, e); return; } ClusteredSipApplicationSession session = findLocalSipApplicationSession(sipApplicationSessionKey, false); if (session != null) { boolean localCall = false; // call is due to remote event boolean localOnly = true; // don't call back into cache boolean notify = false; // SRV.10.7 gives us leeway // not to notify listeners, // which is safer // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); synchronized (session) { session.removeAttributeInternal(attrKey, localCall, localOnly, notify); } if (logger.isDebugEnabled()) log_ .debug("processRemoteAttributeRemoval: removed attribute " + attrKey + " from " + realId); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } } } public void processRemoteSipSessionAttributeRemoval(String realId, String attrKey) { SipSessionKey sipSessionKey; try { sipSessionKey = SessionManagerUtil.parseSipSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip session key " + realId, e); return; } ClusteredSipSession session = findLocalSipSession(sipSessionKey, false, null); if (session != null) { boolean localCall = false; // call is due to remote event boolean localOnly = true; // don't call back into cache boolean notify = false; // SRV.10.7 gives us leeway // not to notify listeners, // which is safer // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); synchronized (session) { session.removeAttributeInternal(attrKey, localCall, localOnly, notify); } if (logger.isDebugEnabled()) log_ .debug("processRemoteAttributeRemoval: removed attribute " + attrKey + " from " + realId); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } } } public void processRemoteInvalidation(String realId) { // Remove the session from our local map ClusteredSession session = (ClusteredSession) sessions_.remove(realId); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSessions_.remove(realId) != null) { if (logger.isDebugEnabled()) log_.debug("Removed entry for session " + realId + " from unloaded session map"); } } else { // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); session.expire(notify, localCall, localOnly); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } // Remove any stats for this session stats_.removeStats(realId); // Update counter. activeCounter_--; } } public void processRemoteSipApplicationSessionInvalidation(String realId) { // Remove the session from our local map SipApplicationSessionKey sipApplicationSessionKey; try { sipApplicationSessionKey = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip application session key " + realId, e); return; } ClusteredSipApplicationSession session = (ClusteredSipApplicationSession) sipManagerDelegate.removeSipApplicationSession(sipApplicationSessionKey); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSipApplicationSessions_.remove(realId) != null) { if (logger.isDebugEnabled()) log_.debug("Removed entry for session " + realId + " from unloaded session map"); } } else { // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); session.expire(notify, localCall, localOnly); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } // Remove any stats for this session stats_.removeStats(realId); // Update counter. activeCounter_--; } } public void processRemoteSipSessionInvalidation(String realId) { // Remove the session from our local map SipSessionKey sipSessionKey; try { sipSessionKey = SessionManagerUtil.parseSipSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip session key " + realId, e); return; } ClusteredSipSession session = (ClusteredSipSession) sipManagerDelegate.removeSipSession(sipSessionKey); if (session == null) { // We weren't managing the session anyway. But remove it // from the list of cached sessions we haven't loaded if (unloadedSipSessions_.remove(realId) != null) { if (logger.isDebugEnabled()) log_.debug("Removed entry for session " + realId + " from unloaded session map"); } } else { //remove the dialog from the local stacks session.getSessionCreatingDialog().delete(); // Expire the session // DON'T SYNCHRONIZE ON SESSION HERE -- isValid() and // expire() are meant to be multi-threaded and synchronize // properly internally; synchronizing externally can lead // to deadlocks!! boolean notify = false; // Don't notify listeners. SRV.10.7 // allows this, and sending notifications // leads to all sorts of issues; e.g. // circular calls with ClusteredSSO boolean localCall = false; // this call originated from the cache; // we have already removed session boolean localOnly = true; // Don't pass attr removals to cache // Ensure the correct TCL is in place ClassLoader prevTcl = Thread.currentThread() .getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(tcl_); session.expire(notify, localCall, localOnly); } finally { Thread.currentThread().setContextClassLoader(prevTcl); } // Remove any stats for this session stats_.removeStats(realId); // Update counter. activeCounter_--; } } public void processLocalPojoModification(String realId) { ClusteredSession session = findLocalSession(realId); if (session != null) { if (logger.isDebugEnabled()) { log_.debug("Marking attributes of session " + realId + " dirty due to POJO modification"); } session.sessionAttributesDirty(); } } public void processSipApplicationSessionLocalPojoModification(String realId) { SipApplicationSessionKey sipApplicationSessionKey; try { sipApplicationSessionKey = SessionManagerUtil.parseSipApplicationSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip application session key " + realId, e); return; } ClusteredSipApplicationSession session = findLocalSipApplicationSession(sipApplicationSessionKey, false); if (session != null) { if (logger.isDebugEnabled()) { log_.debug("Marking attributes of session " + realId + " dirty due to POJO modification"); } session.sessionAttributesDirty(); } } public void processSipSessionLocalPojoModification(String realId) { SipSessionKey sipSessionKey; try { sipSessionKey = SessionManagerUtil.parseSipSessionKey(realId); } catch (ParseException e) { logger.error("Unexpected exception while parsing the following sip session key " + realId, e); return; } ClusteredSipSession session = findLocalSipSession(sipSessionKey, false, null); if (session != null) { if (logger.isDebugEnabled()) { log_.debug("Marking attributes of session " + realId + " dirty due to POJO modification"); } session.sessionAttributesDirty(); } } /** * Gets the session id with any jvmRoute removed. * * @param id * a session id with or without an appended jvmRoute. Cannot be * <code>null</code>. */ protected String getRealId(String id) { return (useJK_ ? Util.getRealId(id) : id); } /** * Callback from the CacheListener to notify us that a session we haven't * loaded has been changed. * * @param realId * the session id, without any trailing jvmRoute * @param dataOwner * the owner of the session. Can be <code>null</code> if the * owner is unknown. */ protected void unloadedSessionChanged(String realId, String dataOwner) { Object obj = unloadedSessions_.put(realId, new OwnedSessionUpdate( dataOwner, System.currentTimeMillis())); if (logger.isDebugEnabled()) { if (obj == null) { log_.debug("New session " + realId + " added to unloaded session map"); } else { log_.debug("Updated timestamp for unloaded session " + realId); } } } /** * Callback from the CacheListener to notify us that a session we haven't * loaded has been changed. * * @param realId * the session id, without any trailing jvmRoute * @param dataOwner * the owner of the session. Can be <code>null</code> if the * owner is unknown. */ protected void unloadedSipSessionChanged(String realId, String dataOwner) { Object obj = unloadedSipSessions_.put(realId, new OwnedSessionUpdate( dataOwner, System.currentTimeMillis())); if (logger.isDebugEnabled()) { if (obj == null) { log_.debug("New session " + realId + " added to unloaded session map"); } else { log_.debug("Updated timestamp for unloaded session " + realId); } } } /** * Callback from the CacheListener to notify us that a session we haven't * loaded has been changed. * * @param realId * the session id, without any trailing jvmRoute * @param dataOwner * the owner of the session. Can be <code>null</code> if the * owner is unknown. */ protected void unloadedSipApplicationSessionChanged(String realId, String dataOwner) { Object obj = unloadedSessions_.put(realId, new OwnedSessionUpdate( dataOwner, System.currentTimeMillis())); if (logger.isDebugEnabled()) { if (obj == null) { log_.debug("New session " + realId + " added to unloaded session map"); } else { log_.debug("Updated timestamp for unloaded session " + realId); } } } // ---------------------------------------------------- Lifecyle Unembedded /** * Start this Manager when running embedded in JBoss AS. * * @throws org.apache.catalina.LifecycleException */ private void startEmbedded() throws LifecycleException { startManager(); // Start the JBossCacheService // Will need to pass the classloader that is associated with this // web app so de-serialization will work correctly. tcl_ = super.getContainer().getLoader().getClassLoader(); proxy_.start(tcl_, this); tm = proxy_.getTransactionManager(); if (tm == null) { throw new LifecycleException( "JBossCacheManager.start(): Obtain null tm"); } try { initializeUnloadedSessions(); // initializeUnloadedSipSessions(); initializeUnloadedSipApplicationSessions(); // Setup our SnapshotManager initSnapshotManager(); // Add SnapshotValve and, if needed, JvmRouteValve and batch repl // valve installValves(); log_.debug("start(): JBossCacheService started"); } catch (Exception e) { log_.error("Unable to start manager.", e); throw new LifecycleException(e); } } // ----------------------------------------------- Lifecyle When Unembedded /** * Start this Manager when running in standalone Tomcat. */ private void startUnembedded() throws LifecycleException { if (started_) { return; } if (log_.isInfoEnabled()) { log_.info("Manager is about to start"); } // Notify our interested LifecycleListeners lifecycle_.fireLifecycleEvent(BEFORE_START_EVENT, this); if (snapshotMode_ == null) { // We were not instantiated by a JBossCacheCluster, so we need to // find one and let it configure our cluster-wide properties try { JBossCacheCluster cluster = (JBossCacheCluster) container_ .getCluster(); cluster.configureManager(this); } catch (ClassCastException e) { String msg = "Cluster is not an instance of JBossCacheCluster"; log_.error(msg, e); throw new LifecycleException(msg, e); } } // Validate attributes if ("SET_AND_GET".equalsIgnoreCase(replTriggerString_)) this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET_AND_GET; else if ("SET_AND_NON_PRIMITIVE_GET" .equalsIgnoreCase(replTriggerString_)) this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET; else if ("SET".equalsIgnoreCase(replTriggerString_)) this.invalidateSessionPolicy_ = WebMetaData.SESSION_INVALIDATE_SET; else throw new LifecycleException("replication-trigger value set to a " + "non-valid value: '" + replTriggerString_ + "' (should be ['SET_AND_GET', " + "'SET_AND_NON_PRIMITIVE_GET', 'SET'])"); if ("SESSION".equalsIgnoreCase(replGranularityString_)) this.replicationGranularity_ = WebMetaData.REPLICATION_GRANULARITY_SESSION; else if ("ATTRIBUTE".equalsIgnoreCase(replGranularityString_)) this.replicationGranularity_ = WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE; else if ("FIELD".equalsIgnoreCase(replGranularityString_)) this.replicationGranularity_ = WebMetaData.REPLICATION_GRANULARITY_FIELD; else throw new LifecycleException( "replication-granularity value set to " + "a non-valid value: '" + replGranularityString_ + "' (should be ['SESSION', " + "'ATTRIBUTE' or 'FIELD'])"); // Create the JBossCacheService try { proxy_ = new ConvergedJBossCacheService(cacheObjectNameString_); // Confirm our replication granularity is compatible with the cache // Throws ISE if not validateFieldMarshalling(); // We need to pass the classloader that is associated with this // web app so de-serialization will work correctly. tcl_ = container_.getLoader().getClassLoader(); proxy_.start(tcl_, this); } catch (Throwable t) { String str = "Problem starting JBossCacheService for Tomcat clustering"; log_.error(str, t); throw new LifecycleException(str, t); } tm = proxy_.getTransactionManager(); if (tm == null) { throw new LifecycleException( "JBossCacheManager.start(): Obtain null tm"); } try { initializeUnloadedSessions(); // initializeUnloadedSipSessions(); initializeUnloadedSipApplicationSessions(); // Add SnapshotValve and, if needed, JvmRouteValve and batch repl // valve installValves(); started_ = true; // Notify our interested LifecycleListeners lifecycle_.fireLifecycleEvent(AFTER_START_EVENT, this); if (log_.isDebugEnabled()) { log_.debug("start(): JBossCacheService started"); } } catch (Exception e) { log_.error("Unable to start manager.", e); throw new LifecycleException(e); } try { registerMBeans(); } catch (Exception e) { log_.error("Could not register ManagerMBean with MBeanServer", e); } } /** * Register this Manager with JMX. */ private void registerMBeans() { try { MBeanServer server = getMBeanServer(); String domain; if (container_ instanceof ContainerBase) { domain = ((ContainerBase) container_).getDomain(); } else { domain = server.getDefaultDomain(); } String hostName = ((Host) container_.getParent()).getName(); hostName = (hostName == null) ? "localhost" : hostName; ObjectName clusterName = new ObjectName(domain + ":service=ClusterManager,WebModule=//" + hostName + ((Context) container_).getPath()); if (server.isRegistered(clusterName)) { log_.warn("MBean " + clusterName + " already registered"); return; } objectName_ = clusterName; server.registerMBean(this, clusterName); } catch (Exception ex) { log_.error(ex.getMessage(), ex); } } /** * Unregister this Manager from the JMX server. */ private void unregisterMBeans() { if (mserver_ != null) { try { mserver_.unregisterMBean(objectName_); } catch (Exception e) { log_.error(e); } } } /** * Get the current MBean Server. * * @return * @throws Exception */ private MBeanServer getMBeanServer() throws Exception { if (mserver_ == null) { mserver_ = MBeanServerLocator.locateJBoss(); } return (mserver_); } /** * Gets the ids of all sessions in the distributed cache and adds them to * the unloaded sessions map, with the current time as the last replication * time. This means these sessions may not be evicted from the cache for a * period well beyond when they would normally expire, but this is a * necessary tradeoff to avoid deserializing them all to check their * lastAccessedTime. */ private void initializeUnloadedSessions() throws CacheException { Map sessions = proxy_.getSessionIds(); if (sessions != null) { long now = System.currentTimeMillis(); for (Iterator it = sessions.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Entry) it.next(); unloadedSessions_.put(entry.getKey(), new OwnedSessionUpdate( (String) entry.getValue(), now)); } } } /** * Gets the ids of all sessions in the distributed cache and adds them to * the unloaded sessions map, with the current time as the last replication * time. This means these sessions may not be evicted from the cache for a * period well beyond when they would normally expire, but this is a * necessary tradeoff to avoid deserializing them all to check their * lastAccessedTime. */ // private void initializeUnloadedSipSessions() throws CacheException { // Map sessions = proxy_.getSipSessionIds(); // if (sessions != null) { // long now = System.currentTimeMillis(); // for (Iterator it = sessions.entrySet().iterator(); it.hasNext();) { // Map.Entry entry = (Entry) it.next(); // unloadedSipSessions_.put(entry.getKey(), new OwnedSessionUpdate( // (String) entry.getValue(), now)); // } // } // } // /** * Gets the ids of all sessions in the distributed cache and adds them to * the unloaded sessions map, with the current time as the last replication * time. This means these sessions may not be evicted from the cache for a * period well beyond when they would normally expire, but this is a * necessary tradeoff to avoid deserializing them all to check their * lastAccessedTime. */ private void initializeUnloadedSipApplicationSessions() throws CacheException { Map sessions = proxy_.getSipApplicationSessionIds(); if (sessions != null) { long now = System.currentTimeMillis(); for (Iterator it = sessions.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Entry) it.next(); unloadedSipApplicationSessions_.put(entry.getKey(), new OwnedSessionUpdate( (String) entry.getValue(), now)); } } } /** * Instantiate a SnapshotManager and ClusteredSessionValve and add the valve * to our parent Context's pipeline. Add a JvmRouteValve and * BatchReplicationClusteredSessionValve if needed. * */ private void installValves() { if (useJK_) { log_.info("We are using mod_jk(2) for load-balancing. " + "Will add JvmRouteValve."); installContextValve(new ConvergedJvmRouteValve(this)); } // Add batch replication valve if needed. // TODO -- should we add this even if not FIELD in case a cross-context // call traverses a field-based webapp? if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_FIELD && Boolean.TRUE.equals(replicationFieldBatchMode_)) { Valve batchValve = new BatchReplicationClusteredSessionValve(this); log_ .debug("Adding BatchReplicationClusteredSessionValve for batch replication."); installContextValve(batchValve); } // Add clustered session valve ConvergedClusteredSessionValve valve = new ConvergedClusteredSessionValve(this); installContextValve(valve); } /** * Create and start a snapshot manager. */ private void initSnapshotManager() { String ctxPath = ((Context) container_).getPath(); if ("instant".equals(snapshotMode_) || replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_FIELD) { snapshotManager_ = new InstantConvergedSnapshotManager(this, ctxPath); } else if ("interval".equals(snapshotMode_)) { snapshotManager_ = new IntervalConvergedSnapshotManager(this, ctxPath, snapshotInterval_); } else { log_.error("Snapshot mode must be 'instant' or 'interval' - " + "using 'instant'"); snapshotManager_ = new InstantConvergedSnapshotManager(this, ctxPath); } snapshotManager_.start(); } private void installContextValve(Valve valve) { boolean installed = false; // In embedded mode, install the valve via JMX to be consistent // with the way the overall context is created in TomcatDeployer. // We can't do this in unembedded mode because we are called // before our Context is registered with the MBean server if (embedded_ && getContextObjectName() != null) { try { getMBeanServer().invoke(getContextObjectName(), "addValve", new Object[] { valve }, new String[] { "org.apache.catalina.Valve" }); installed = true; } catch (Exception e) { // JBAS-2422. If the context is restarted via JMX, the above // JMX call will fail as the context will not be registered // when it's made. So we catch the exception and fall back // to adding the valve directly. // TODO consider skipping adding via JMX and just do it directly log_.debug("Caught exception installing valve to Context", e); } } if (!installed) { // If possible install via the ContainerBase.addValve() API. if (container_ instanceof ContainerBase) { ((ContainerBase) container_).addValve(valve); } else { // No choice; have to add it to the context's pipeline container_.getPipeline().addValve(valve); } } } /** * If we are using FIELD granularity, checks that the TreeCache supports * marshalling. * * @throws IllegalStateException * if not */ private void validateFieldMarshalling() { if (replicationGranularity_ == WebMetaData.REPLICATION_GRANULARITY_FIELD && !proxy_.isMarshallingAvailable()) { // BES 16/8/2006 -- throw ISE, not ClusteringNotSupportedException, // as a // misconfig should be treated differently from the absence of // clustering // services throw new IllegalStateException( "replication-granularity value is set to " + "'FIELD' but is not supported by the cache service configuration. " + "Must set 'UseRegionBasedMarshalling' to 'true' in the tc5-cluster.sar jboss-service.xml"); } } private ObjectName getContextObjectName() { String oname = container_.getObjectName(); try { return (oname == null) ? null : new ObjectName(oname); } catch (MalformedObjectNameException e) { log_.warn("Error creating object name from string " + oname, e); return null; } } private class OwnedSessionUpdate { String owner; long updateTime; OwnedSessionUpdate(String owner, long updateTime) { this.owner = owner; this.updateTime = updateTime; } } public Session createEmptySession() { return createEmptyClusteredSession(); } private ClusteredSession createEmptyClusteredSession() { log_.debug("Creating an empty ClusteredSession"); ClusteredSession session = null; switch (replicationGranularity_) { case (WebMetaData.REPLICATION_GRANULARITY_ATTRIBUTE): { if (super.container_ instanceof SipContext) { session = new ConvergedAttributeBasedClusteredSession(this); } else { session = new AttributeBasedClusteredSession(this); } break; } case (WebMetaData.REPLICATION_GRANULARITY_FIELD): { if (super.container_ instanceof SipContext) { session = new ConvergedFieldBasedClusteredSession(this); } else { session = new FieldBasedClusteredSession(this); } break; } default: if (super.container_ instanceof SipContext) { session = new ConvergedSessionBasedClusteredSession(this); } else { session = new SessionBasedClusteredSession(this); } break; } return session; } /** * @return the SipFactoryImpl */ public SipFactoryImpl getSipFactoryImpl() { return sipManagerDelegate.getSipFactoryImpl(); } /** * @param sipFactoryImpl * the SipFactoryImpl to set */ public void setSipFactoryImpl(SipFactoryImpl sipFactoryImpl) { sipManagerDelegate.setSipFactoryImpl(sipFactoryImpl); } /** * @return the container */ public Container getContainer() { return sipManagerDelegate.getContainer(); } /** * @param container * the container to set */ public void setContainer(Container container) { container_ = container; sipManagerDelegate.setContainer(container); } /** * {@inheritDoc} */ public MobicentsSipSession removeSipSession(final SipSessionKey key) { ClusteredSipSession clusterSess = (ClusteredSipSession) sipManagerDelegate.removeSipSession(key); if(clusterSess == null) { return null; } synchronized (clusterSess) { String realId = clusterSess.getId(); if (log_.isDebugEnabled()) { log_.debug("Removing session from store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startSipCacheActivity(); clusterSess.removeMyself(); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipSessionExpired(clusterSess, realId, snapshotManager_); stats_.removeStats(realId); activeCounter_--; } } return clusterSess; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession removeSipApplicationSession( final SipApplicationSessionKey key) { ClusteredSipApplicationSession clusterSess = (ClusteredSipApplicationSession) sipManagerDelegate.removeSipApplicationSession(key); if(clusterSess == null) { return null; } synchronized (clusterSess) { String realId = clusterSess.getId(); if (log_.isDebugEnabled()) { log_.debug("Removing session from store with id: " + realId); } try { // Ignore any cache notifications that our own work generates ConvergedSessionReplicationContext.startSipCacheActivity(); clusterSess.removeMyself(); } finally { ConvergedSessionReplicationContext.finishSipCacheActivity(); // We don't want to replicate this session at the end // of the request; the removal process took care of that ConvergedSessionReplicationContext.sipApplicationSessionExpired(clusterSess, realId, snapshotManager_); stats_.removeStats(realId); activeCounter_--; } } return clusterSess; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession getSipApplicationSession( final SipApplicationSessionKey key, final boolean create) { // Find it from the local store first ClusteredSipApplicationSession session = findLocalSipApplicationSession(key, false); // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy // replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !ConvergedSessionReplicationContext .isSipApplicationSessionBoundAndExpired(key.toString(), snapshotManager_)) { if (logger.isDebugEnabled()) log_.debug("Checking for sip app session " + key + " in the distributed cache"); session = loadSipApplicationSession(key, create); if (session != null) { add(session); // TODO should we advise of a new session? // tellNew(); } } else if (session != null && session.isOutdated()) { if (logger.isDebugEnabled()) log_.debug("Updating session " + key + " from the distributed cache"); // Need to update it from the cache loadSipApplicationSession(key, create); } if (session != null) { // Add this session to the set of those potentially needing // replication ConvergedSessionReplicationContext.bindSipApplicationSession(session, snapshotManager_); } return session; } /** * {@inheritDoc} */ public MobicentsSipSession getSipSession(final SipSessionKey key, final boolean create, final SipFactoryImpl sipFactoryImpl, final MobicentsSipApplicationSession sipApplicationSessionImpl) { // Find it from the local store first ClusteredSipSession session = findLocalSipSession(key, false, sipApplicationSessionImpl); if(session == null) { if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " not found in the local store"); } } else { if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " found in the local store " + session); } } boolean isSipSessionBoundAndExpired = ConvergedSessionReplicationContext .isSipSessionBoundAndExpired(key.toString(), snapshotManager_); if (logger.isDebugEnabled()) { logger.debug("sip session " + key + " bound and expired ? " + isSipSessionBoundAndExpired); } // If we didn't find it locally, only check the distributed cache // if we haven't previously handled this session id on this request. // If we handled it previously but it's no longer local, that means // it's been invalidated. If we request an invalidated session from // the distributed cache, it will be missing from the local cache but // may still exist on other nodes (i.e. if the invalidation hasn't // replicated yet because we are running in a tx). With buddy // replication, // asking the local cache for the session will cause the out-of-date // session from the other nodes to be gravitated, thus resuscitating // the session. if (session == null && !isSipSessionBoundAndExpired) { if (logger.isDebugEnabled()) logger.debug("Checking for sip session " + key + " in the distributed cache"); session = loadSipSession(key, create, sipFactoryImpl, sipApplicationSessionImpl); if (session != null) { add(session); // TODO should we advise of a new session? // tellNew(); } } else if (session != null && session.isOutdated()) { if (logger.isDebugEnabled()) logger.debug("Updating session " + key + " from the distributed cache"); // Need to update it from the cache loadSipSession(key, create, sipFactoryImpl, sipApplicationSessionImpl); } if (session != null) { // Add this session to the set of those potentially needing // replication ConvergedSessionReplicationContext.bindSipSession(session, snapshotManager_); } return session; } /** * {@inheritDoc} */ public MobicentsSipApplicationSession findSipApplicationSession( HttpSession httpSession) { return sipManagerDelegate.findSipApplicationSession(httpSession); } /** * */ public void dumpSipSessions() { sipManagerDelegate.dumpSipSessions(); } /** * */ public void dumpSipApplicationSessions() { sipManagerDelegate.dumpSipApplicationSessions(); } /** * {@inheritDoc} */ public Iterator<MobicentsSipSession> getAllSipSessions() { return sipManagerDelegate.getAllSipSessions(); } /** * {@inheritDoc} */ public Iterator<MobicentsSipApplicationSession> getAllSipApplicationSessions() { return sipManagerDelegate.getAllSipApplicationSessions(); } /** * {@inheritDoc} */ public void removeAllSessions() { sipManagerDelegate.removeAllSessions(); } /** * @return the snapshotManager_ */ public SnapshotManager getSnapshotManager() { return snapshotManager_; } // JMX Statistics /** * Return descriptive information about this Manager implementation and the * corresponding version number, in the format * <code><description>/<version></code>. */ public String getInfo() { return (info); } /** * Return the maximum number of active Sessions allowed, or -1 for no limit. */ public int getMaxActiveSipSessions() { return (this.sipManagerDelegate.getMaxActiveSipSessions()); } /** * Set the maximum number of actives Sip Sessions allowed, or -1 for no * limit. * * @param max * The new maximum number of sip sessions */ public void setMaxActiveSipSessions(int max) { this.sipManagerDelegate.setMaxActiveSipSessions(max); } /** * Return the maximum number of active Sessions allowed, or -1 for no limit. */ public int getMaxActiveSipApplicationSessions() { return (this.sipManagerDelegate.getMaxActiveSipApplicationSessions()); } /** * Set the maximum number of actives Sip Application Sessions allowed, or -1 * for no limit. * * @param max * The new maximum number of sip application sessions */ public void setMaxActiveSipApplicationSessions(int max) { this.sipManagerDelegate.setMaxActiveSipApplicationSessions(max); } /** * Number of sip session creations that failed due to maxActiveSipSessions * * @return The count */ public int getRejectedSipSessions() { return sipManagerDelegate.getRejectedSipSessions(); } public void setRejectedSipSessions(int rejectedSipSessions) { this.sipManagerDelegate.setRejectedSipSessions(rejectedSipSessions); } /** * Number of sip session creations that failed due to maxActiveSipSessions * * @return The count */ public int getRejectedSipApplicationSessions() { return sipManagerDelegate.getRejectedSipApplicationSessions(); } public void setRejectedSipApplicationSessions( int rejectedSipApplicationSessions) { this.sipManagerDelegate.setRejectedSipApplicationSessions(rejectedSipApplicationSessions); } public void setSipSessionCounter(int sipSessionCounter) { this.sipManagerDelegate.setSipSessionCounter(sipSessionCounter); } /** * Total sessions created by this manager. * * @return sessions created */ public int getSipSessionCounter() { return sipManagerDelegate.getSipSessionCounter(); } /** * Returns the number of active sessions * * @return number of sessions active */ public int getActiveSipSessions() { return sipManagerDelegate.getNumberOfSipSessions(); } /** * Gets the longest time (in seconds) that an expired session had been * alive. * * @return Longest time (in seconds) that an expired session had been alive. */ public int getSipSessionMaxAliveTime() { return sipManagerDelegate.getSipSessionMaxAliveTime(); } /** * Sets the longest time (in seconds) that an expired session had been * alive. * * @param sessionMaxAliveTime * Longest time (in seconds) that an expired session had been * alive. */ public void setSipSessionMaxAliveTime(int sipSessionMaxAliveTime) { this.sipManagerDelegate.setSipSessionMaxAliveTime(sipSessionMaxAliveTime); } /** * Gets the average time (in seconds) that expired sessions had been alive. * * @return Average time (in seconds) that expired sessions had been alive. */ public int getSipSessionAverageAliveTime() { return sipManagerDelegate.getSipSessionAverageAliveTime(); } /** * Sets the average time (in seconds) that expired sessions had been alive. * * @param sessionAverageAliveTime * Average time (in seconds) that expired sessions had been * alive. */ public void setSipSessionAverageAliveTime(int sipSessionAverageAliveTime) { this.sipManagerDelegate.setSipSessionAverageAliveTime(sipSessionAverageAliveTime); } public void setSipApplicationSessionCounter(int sipApplicationSessionCounter) { this.sipManagerDelegate.setSipApplicationSessionCounter(sipApplicationSessionCounter); } /** * Total sessions created by this manager. * * @return sessions created */ public int getSipApplicationSessionCounter() { return sipManagerDelegate.getSipApplicationSessionCounter(); } /** * Returns the number of active sessions * * @return number of sessions active */ public int getActiveSipApplicationSessions() { return sipManagerDelegate.getNumberOfSipApplicationSessions(); } /** * Gets the longest time (in seconds) that an expired session had been * alive. * * @return Longest time (in seconds) that an expired session had been alive. */ public int getSipApplicationSessionMaxAliveTime() { return sipManagerDelegate.getSipApplicationSessionMaxAliveTime(); } /** * Sets the longest time (in seconds) that an expired session had been * alive. * * @param sessionMaxAliveTime * Longest time (in seconds) that an expired session had been * alive. */ public void setSipApplicationSessionMaxAliveTime( int sipApplicationSessionMaxAliveTime) { this.sipManagerDelegate.setSipApplicationSessionMaxAliveTime(sipApplicationSessionMaxAliveTime); } /** * Gets the average time (in seconds) that expired sessions had been alive. * * @return Average time (in seconds) that expired sessions had been alive. */ public int getSipApplicationSessionAverageAliveTime() { return sipManagerDelegate.getSipApplicationSessionAverageAliveTime(); } /** * Sets the average time (in seconds) that expired sessions had been alive. * * @param sessionAverageAliveTime * Average time (in seconds) that expired sessions had been * alive. */ public void setSipApplicationSessionAverageAliveTime( int sipApplicationSessionAverageAliveTime) { this.sipManagerDelegate.setSipApplicationSessionAverageAliveTime(sipApplicationSessionAverageAliveTime); } /** * Gets the number of sessions that have expired. * * @return Number of sessions that have expired */ public int getExpiredSipSessions() { return sipManagerDelegate.getExpiredSipSessions(); } /** * Sets the number of sessions that have expired. * * @param expiredSessions * Number of sessions that have expired */ public void setExpiredSipSessions(int expiredSipSessions) { this.sipManagerDelegate.setExpiredSipSessions(expiredSipSessions); } /** * Gets the number of sessions that have expired. * * @return Number of sessions that have expired */ public int getExpiredSipApplicationSessions() { return sipManagerDelegate.getExpiredSipApplicationSessions(); } /** * Sets the number of sessions that have expired. * * @param expiredSessions * Number of sessions that have expired */ public void setExpiredSipApplicationSessions( int expiredSipApplicationSessions) { this.sipManagerDelegate.setExpiredSipApplicationSessions(expiredSipApplicationSessions); } }