| AttributeBasedClusteredSession.java |
/*
* JBoss, the OpenSource WebOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.web.tomcat.tc5.session;
import org.jboss.metadata.WebMetaData;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.security.Principal;
import java.util.*;
/**
* Implementation of a clustered session for the JBossCacheManager. 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>
* /JSESSION
* /web_app_path (path + session id is unique)
* /id 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 Ben Wang
* @version $Revision: 1.3.2.5 $
*/
class AttributeBasedClusteredSession
extends ClusteredSession implements Serializable
{
static final long serialVersionUID = -5625209785550936713L;
/**
* Descriptive information describing this Session implementation.
*/
protected static final String info = "AttributeBasedClusteredSession/1.0";
private transient boolean isSessionModifiedSinceLastSave_;
private transient JBossCacheService proxy_;
// Transient map to store attr changes for replication.
private transient Map attrModifiedMap_;
// Note that the removed attr is intentionally stored in a map instead of set so it is faster to lookup and remove.
private transient Map attrRemovedMap_;
private static int REMOVE = 0; // Used to track attribute changes
private static int MODIFY = 1;
private transient Map attributes_;
public AttributeBasedClusteredSession(AbstractJBossManager manager)
{
super(manager);
initAfterLoad(manager);
}
/**
* Initialize fields marked as transient after loading this session
* from the distributed store
*
* @param manager the manager for this session
*/
public void initAfterLoad(AbstractJBossManager manager)
{
// Use proxy to determine if this is first time session retrieval.
if (this.proxy_ == null)
{
setManager(manager);
listeners = new ArrayList();
notes = new HashMap();
support = new PropertyChangeSupport(this);
expiring = false;
attributes_ = Collections.synchronizedMap(new HashMap());
attrModifiedMap_ = new HashMap();
attrRemovedMap_ = new HashMap();
// cache invalidate purpose
isOutdated = false;
proxy_ = ((JBossCacheManager) manager).getCacheService();
// still null???
if (proxy_ == null)
{
throw new RuntimeException("SessionBasedClusteredSession: Cache service is null.");
}
// Notify all attributes of type HttpSessionActivationListener (SRV 7.7.2)
this.activate();
}
// Since attribute maps are transient, we will need to populate it from the underlying store.
populateAttributes();
}
/**
* Populate the attributes stored in the distributed store to local transient ones.
*/
protected void populateAttributes()
{
Map map = proxy_.getAttributes(id);
if (map.size() != 0) attributes_ = map;
}
// ----------------------------------------------------- Session Properties
/**
* Set the creation time for this session. This method is called by the
* Manager when an existing Session instance is reused.
*
* @param time The new creation time
*/
public void setCreationTime(long time)
{
super.setCreationTime(time);
sessionIsDirty();
}
/**
* Set the authenticated Principal that is associated with this Session.
* This provides an <code>Authenticator</code> with a means to cache a
* previously authenticated Principal, and avoid potentially expensive
* <code>Realm.authenticate()</code> calls on every request.
*
* @param principal The new Principal, or <code>null</code> if none
*/
public void setPrincipal(Principal principal)
{
Principal oldPrincipal = this.principal;
this.principal = principal;
support.firePropertyChange("principal", oldPrincipal, this.principal);
if ((oldPrincipal != null && !oldPrincipal.equals(principal)) ||
(oldPrincipal == null && principal != null))
sessionIsDirty();
}
// ------------------------------------------------- Session Public Methods
/**
* Return a string representation of this object.
*/
public String toString()
{
StringBuffer sb = new StringBuffer();
sb.append("AttributeBasedClusteredSession[");
sb.append(id);
sb.append("]");
return (sb.toString());
}
/**
* Start to process my local attribute changes to the replication layer.
*/
public synchronized void processSessionRepl()
{
if (!isSessionDirty())
{
if (log.isDebugEnabled())
{
log.debug("processSessionRepl(): session is not dirty. No need to replicate.");
}
return;
}
// Replicate this first. Note this will be lightweight since many of the attributes are transient.
// And also without attributes
if (log.isDebugEnabled())
{
log.debug("processSessionRepl(): session is dirty. Will increment version from: " +
getVersion() + " and replicate.");
}
this.incrementVersion();
proxy_.putSession(id, this);
// Go thru the attribute change list
// Go thru the remove attr list first
{
Set set = attrModifiedMap_.keySet();
Iterator it = set.iterator();
while (it.hasNext())
{
Object key = it.next();
proxy_.putAttribute(id, (String) key, attrModifiedMap_.get(key));
}
}
// Go thru the remove attr list
{
Set set = attrRemovedMap_.keySet();
Iterator it = set.iterator();
while (it.hasNext())
{
Object key = it.next();
proxy_.removeAttribute(id, (String) key);
}
}
clearAttrChangedMap();
isSessionModifiedSinceLastSave_ = false;
}
public void removeMyself()
{
// This is a shortcut to remove session and it's child attributes.
proxy_.removeSession(id);
if (attributes_ != null)
attributes_.clear();
}
public void removeMyselfLocal()
{
// Need to evict attribute first before session to clean up everything.
proxy_.removeAttributeLocal(id);
proxy_.removeSessionLocal(id);
if (attributes_ != null)
attributes_.clear();
}
// ----------------------------------------------HttpSession Public Methods
public void access()
{
super.access();
// If we do not use the local cache the session is dirty
// after every access.
if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_ACCESS)
{
this.sessionIsDirty();
}
}
// ------------------------------------------------ JBoss internal abstract method
protected Object getJBossInternalAttribute(String name)
{
// Check the accumulate change maps first.
Object result = null;
// TODO Need to check if underlying store is dirty. This will be done with listener in the future.
result = attributes_.get(name);
if (result != null)
{
int invalidationPolicy = ((AbstractJBossManager) this.manager).getInvalidateSessionPolicy();
if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_SET_AND_GET)
{
attributeChanged(name, result, MODIFY);
}
else if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET)
{
if (!(result instanceof String ||
result instanceof Integer ||
result instanceof Long ||
result instanceof Byte ||
result instanceof Short ||
result instanceof Float ||
result instanceof Double ||
result instanceof Character ||
result instanceof Boolean)
)
{
attributeChanged(name, result, MODIFY);
}
}
}
return result;
}
protected Object removeJBossInternalAttribute(String name)
{
Object result = attributes_.remove(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)
{
attributes_.put(key, value);
attributeChanged(key, value, MODIFY);
return value;
}
protected void sessionIsDirty()
{
// Session is dirty
isSessionModifiedSinceLastSave_ = true;
}
public boolean isSessionDirty()
{
// Need to check if the attr change map is empty as well??
return isSessionModifiedSinceLastSave_;
}
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);
}
sessionIsDirty();
}
protected synchronized void clearAttrChangedMap()
{
attrRemovedMap_.clear();
attrModifiedMap_.clear();
}
}
| AttributeBasedClusteredSession.java |