/* * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.web.tomcat.service.session; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey; import org.mobicents.servlet.sip.startup.SipContext; /** * Implementation of a clustered sip application session for the JBossCacheManager. * This class is based on the following Jboss class org.jboss.web.tomcat.service.session.AttributeBasedClusteredSession JBOSS AS 4.2.2 Tag * * The replication granularity level is attribute based; that is, we replicate only the dirty attributes. * We use JBossCache for our internal, deplicated data store. * The internal structure is like in JBossCache: * <pre> * /SIPSESSION * /hostname * /sip_app_name (path + session id is unique) * /sipapplicationsessionid Map(id, session) * (VERSION_KEY, version) // Used for version tracking. version is an Integer. * /ATTRIBUTE Map(attr_key, value) * </pre> * <p/> * Note that the isolation level of the cache dictates the * concurrency behavior. Also note that session and its associated attribtues are stored in different nodes. * This will be ok since cache will take care of concurrency. When replicating, we will need to replicate both * session and its attributes.</p> * * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * @author Ben Wang * @author Brian Stansberry * */ public class AttributeBasedClusteredSipApplicationSession extends JBossCacheClusteredSipApplicationSession { private static transient Logger logger = Logger.getLogger(AttributeBasedClusteredSipSession.class); protected static final String EMPTY_ARRAY[] = new String[0]; /** * Descriptive information describing this Session implementation. */ protected static final String info = "AttributeBasedClusteredSipApplicationSession/1.0"; // Transient map to store attr changes for replication. private transient Map<Object, Object> attrModifiedMap_ = new HashMap<Object, Object>(); // Note that the removed attr is intentionally stored in a map // instead of a Set so it is faster to lookup and remove. private transient Map<Object, Object> attrRemovedMap_ = new HashMap<Object, Object>(); private static final int REMOVE = 0; // Used to track attribute changes private static final int MODIFY = 1; // TODO why isn't the superclass field sufficient? private transient Map<Object, Object> attributes_ = Collections.synchronizedMap(new HashMap<Object, Object>()); protected AttributeBasedClusteredSipApplicationSession(SipApplicationSessionKey key, SipContext sipContext) { super(key, sipContext); } /** * Override the superclass to additionally reset this class' fields. * <p> * <strong>NOTE:</strong> It is not anticipated that this method will be * called on a ClusteredSession, but we are overriding the method to be * thorough. * </p> */ // public void recycle() { // super.recycle(); // // attributes_.clear(); // clearAttrChangedMaps(); // } /** * Return a string representation of this object. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append("AttributeBasedClusteredSipApplicationSession["); sb.append(super.toString()); sb.append("]"); return (sb.toString()); } /** * Overrides the superclass version to read in the attributes. */ public synchronized void processSessionRepl() { // Replicate the metadata first. Note this will be lightweight since // many // of the fields are transient and the attribute map isn't included. if (logger.isDebugEnabled()) { logger.debug("processSessionRepl(): session is dirty. Will increment " + "version from: " + getVersion() + " and replicate."); } this.incrementVersion(); proxy_.putSipApplicationSession(getId(), this); // Go thru the attribute change list if (getSessionAttributesDirty()) { // Go thru the modified attr list first int modCount = attrModifiedMap_.size(); if (modCount == 1) { for (Iterator it = attrModifiedMap_.entrySet().iterator(); it .hasNext();) { Map.Entry entry = (Entry) it.next(); proxy_.putSipApplicationSessionAttribute(getId(), (String) entry.getKey(), entry .getValue()); } } else if (modCount > 0) { // It's more efficient to write a map than 2 method calls, // plus it reduces the number of CacheListener notifications proxy_.putSipApplicationSessionAttribute(getId(), attrModifiedMap_); } // Go thru the remove attr list if (attrRemovedMap_.size() > 0) { for (Iterator it = attrRemovedMap_.keySet().iterator(); it .hasNext();) { proxy_.removeSipApplicationSessionAttribute(getId(), (String) it.next()); } } clearAttrChangedMaps(); } sessionAttributesDirty = false; sessionMetadataDirty = false; updateLastReplicated(); } public void removeMyself() { // This is a shortcut to remove session and it's child attributes. proxy_.removeSipApplicationSession(getId()); } public void removeMyselfLocal() { // Need to evict attribute first before session to clean up everything. // BRIAN -- the attributes *are* already evicted, but we leave the // removeAttributesLocal call here in order to evict the ATTRIBUTE node. // Otherwise empty nodes for the session root and child ATTRIBUTE will // remain in the tree and screw up our list of session names. proxy_.removeSipApplicationSessionAttributesLocal(getId()); proxy_.removeSipApplicationSessionLocal(getId()); } // ------------------------------------------------ JBoss internal abstract // method /** * Populate the attributes stored in the distributed store to local * transient ones. */ protected void populateAttributes() { Map map = proxy_.getSipApplicationSessionAttributes(getId()); // Preserve any local attributes that were excluded from replication Map excluded = removeExcludedAttributes(attributes_); if (excluded != null) map.putAll(excluded); attributes_ = Collections.synchronizedMap(map); attrModifiedMap_.clear(); attrRemovedMap_.clear(); } protected Object getJBossInternalAttribute(String name) { Object result = attributes_.get(name); // Do dirty check even if result is null, as w/ SET_AND_GET null // still makes us dirty (ensures timely replication w/o using ACCESS) if (isGetDirty(result) && !replicationExcludes.contains(name)) { attributeChanged(name, result, MODIFY); } return result; } protected Object removeJBossInternalAttribute(String name, boolean localCall, boolean localOnly) { Object result = attributes_.remove(name); if (localCall && !replicationExcludes.contains(name)) attributeChanged(name, result, REMOVE); return result; } protected Map getJBossInternalAttributes() { return attributes_; } protected Set getJBossInternalKeys() { return attributes_.keySet(); } /** * Method inherited from Tomcat. Return zero-length based string if not * found. */ protected String[] keys() { return ((String[]) getJBossInternalKeys().toArray(EMPTY_ARRAY)); } protected Object setJBossInternalAttribute(String key, Object value) { Object old = attributes_.put(key, value); if (!replicationExcludes.contains(key)) attributeChanged(key, value, MODIFY); return old; } protected synchronized void attributeChanged(Object key, Object value, int op) { if (op == MODIFY) { if (attrRemovedMap_.containsKey(key)) { attrRemovedMap_.remove(key); } attrModifiedMap_.put(key, value); } else if (op == REMOVE) { if (attrModifiedMap_.containsKey(key)) { attrModifiedMap_.remove(key); } attrRemovedMap_.put(key, value); } sessionAttributesDirty(); } protected synchronized void clearAttrChangedMaps() { attrRemovedMap_.clear(); attrModifiedMap_.clear(); } }