/*
* 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.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.http.HttpSession;
import javax.servlet.sip.SipApplicationSessionActivationListener;
import javax.servlet.sip.SipApplicationSessionAttributeListener;
import javax.servlet.sip.SipApplicationSessionBindingEvent;
import javax.servlet.sip.SipApplicationSessionBindingListener;
import javax.servlet.sip.SipApplicationSessionEvent;
import javax.servlet.sip.SipApplicationSessionListener;
import org.apache.catalina.Globals;
import org.apache.catalina.session.StandardSession;
import org.apache.catalina.util.StringManager;
import org.apache.log4j.Logger;
import org.jboss.metadata.WebMetaData;
import org.mobicents.servlet.sip.core.session.SessionManagerUtil;
import org.mobicents.servlet.sip.core.session.SipApplicationSessionImpl;
import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey;
import org.mobicents.servlet.sip.core.session.SipSessionKey;
import org.mobicents.servlet.sip.startup.SipContext;
/**
* Abstract base class for sip session clustering based on SipApplicationSessionImpl. Different session
* replication strategy can be implemented such as session- field- or attribute-based ones.
*
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*
*/
public abstract class ClusteredSipApplicationSession extends SipApplicationSessionImpl
implements Externalizable {
private static transient Logger logger = Logger.getLogger(ClusteredSipApplicationSession.class);
/**
* Descriptive information describing this Session implementation.
*/
protected static final String info = "ClusteredSipApplicationSession/1.0";
/**
* Set of attribute names which are not allowed to be replicated/persisted.
*/
protected static final String[] excludedAttributes = { Globals.SUBJECT_ATTR };
/**
* Set containing all members of {@link #excludedAttributes}.
*/
protected static final Set replicationExcludes;
static {
HashSet set = new HashSet();
for (int i = 0; i < excludedAttributes.length; i++) {
set.add(excludedAttributes[i]);
}
replicationExcludes = Collections.unmodifiableSet(set);
}
protected int invalidationPolicy;
/**
* If true, means the local in-memory session data contains changes that
* have not been published to the distributed cache.
*
* @deprecated not used
*/
protected transient boolean isSessionModifiedSinceLastSave;
/**
* If true, means the local in-memory session data contains metadata changes
* that have not been published to the distributed cache.
*/
protected transient boolean sessionMetadataDirty;
/**
* If true, means the local in-memory session data contains attribute
* changes that have not been published to the distributed cache.
*/
protected transient boolean sessionAttributesDirty;
/**
* The last version that was passed to {@link #setOutdatedVersion} or
* <code>0</code> if <code>setIsOutdated(false)</code> was subsequently
* called.
*/
protected transient int outdatedVersion;
/**
* The last time {@link #setIsOutdated setIsOutdated(true)} was called or
* <code>0</code> if <code>setIsOutdated(false)</code> was subsequently
* called.
*/
protected transient long outdatedTime;
/**
* Version number to track cache invalidation. If any new version number is
* greater than this one, it means the data it holds is newer than this one.
*/
protected int version;
/**
* The session's id with any jvmRoute removed.
*/
// protected transient String realId;
/**
* Whether JK is being used, in which case our realId will not match our id
*/
private transient boolean useJK;
/**
* Timestamp when we were last replicated.
*/
protected transient long lastReplicated;
/**
* @deprecated Not used
*/
protected transient int maxUnreplicatedFactor = 80;
/**
* Maximum number of milliseconds this session should be allowed to go
* unreplicated if access to the session doesn't mark it as dirty.
*/
protected transient long maxUnreplicatedInterval = 0;
/** True if maxUnreplicatedInterval is 0 or less than maxInactiveInterval */
protected transient boolean alwaysReplicateMetadata = true;
/**
* Whether any of this session's attributes implement
* HttpSessionActivationListener.
*/
protected transient Boolean hasActivationListener;
/**
* Has this session only been accessed once?
*/
protected transient boolean firstAccess;
/**
* this will hold the remaining time at which the session should have been expired at the time it has been passivated
*/
protected transient long futureExpirationTimeOnPassivation;
/**
* the Set of keys of the sip sessions that were present in the sip application session at the time it has been passivated
*/
protected transient Set<SipSessionKey> sipSessionsOnPassivation;
/**
* the Set of realId of the http sessions that were present in the sip application session at the time it has been passivated
*/
protected transient Set<String> httpSessionsOnPassivation;
/**
* The string manager for this package.
*/
protected static StringManager sm = StringManager
.getManager(ClusteredSession.class.getPackage().getName());
protected ClusteredSipApplicationSession(SipApplicationSessionKey key,
SipContext sipContext, boolean useJK) {
super(key, sipContext);
invalidationPolicy = ((AbstractJBossManager)sipContext.getSipManager()).getInvalidateSessionPolicy();
this.useJK = useJK;
this.firstAccess = true;
// it starts with true so that it gets replicated when first created
sessionMetadataDirty = true;
checkAlwaysReplicateMetadata();
sipSessionsOnPassivation = new HashSet<SipSessionKey>();
httpSessionsOnPassivation = new HashSet<String>();
}
/**
* Check to see if the session data is still valid. Outdated here means that
* the in-memory data is not in sync with one in the data store.
*
* @return
*/
public boolean isOutdated() {
return lastAccessedTime < outdatedTime;
}
/**
* Marks this session as outdated or up-to-date vis-a-vis the distributed
* cache.
*
* @param outdated
*
* @deprecated use {@link #setOutdatedVersion(int)} and
* {@link #clearOutdated()}
*/
public void setIsOutdated(boolean outdated) {
if (outdated)
outdatedTime = System.currentTimeMillis();
else
clearOutdated();
}
public void setOutdatedVersion(int version) {
this.outdatedVersion = version;
outdatedTime = System.currentTimeMillis();
}
public void clearOutdated() {
// Only overwrite the access time if access() hasn't been called
// since setOutdatedVersion() was called
//FIXME
// if (outdatedTime > lastAccessedTime) {
// lastAccessedTime = thisAccessedTime;
// thisAccessedTime = outdatedTime;
// }
outdatedTime = 0;
// Only overwrite the version if the outdated version is greater
// Otherwise when we first unmarshal a session that has been
// replicated many times, we will reset the version to 0
if (outdatedVersion > version)
version = outdatedVersion;
outdatedVersion = 0;
}
public void updateAccessTimeFromOutdatedTime() {
//FIXME
// if (outdatedTime > thisAccessedTime) {
// lastAccessedTime = thisAccessedTime;
// thisAccessedTime = outdatedTime;
// }
outdatedTime = 0;
}
/**
* Gets the session id with any appended jvmRoute info removed.
*
* @see #getUseJK()
*/
// public String getRealId() {
// return realId;
// }
//
// private void parseRealId(String sessionId) {
// String newId = null;
// if (useJK)
// newId = Util.getRealId(sessionId);
// else
// newId = sessionId;
//
// // realId is used in a lot of map lookups, so only replace it
// // if the new id is actually different -- preserve object identity
// if (!newId.equals(realId))
// realId = newId;
// }
/**
* This is called specifically for failover case using mod_jk where the new
* session has this node name in there. As a result, it is safe to just
* replace the id since the backend store is using the "real" id without the
* node name.
*
* @param id
*/
//FIXME we don't need this for SIP since the route is the call id and is handled by the LB,
//is it worth encoding it in the SIP message
// public void resetIdWithRouteInfo(String id) {
// this.id = id;
// parseRealId(id);
// }
public boolean getUseJK() {
return useJK;
}
/**
* Check to see if the input version number is greater than I am. If it is,
* it means we will need to invalidate the in-memory cache.
*
* @param version
* @return
*/
public boolean isNewData(int version) {
return (this.version < version);
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
/**
* There are couple ways to generate this version number. But we will stick
* with the simple one of incrementing for now.
*
* @return the new version
*/
public int incrementVersion() {
return version++;
}
/**
* @deprecated Returns a meaningless value; use
* {@link #setMaxUnreplicatedInterval(int)}
*/
public int getMaxUnreplicatedFactor() {
return 80;
}
/**
* @deprecated Ignored; use {@link #setMaxUnreplicatedInterval(int)}
*/
public void setMaxUnreplicatedFactor(int factor) {
}
/**
* Gets the time {@link #updateLastReplicated()} was last called, or
* <code>0</code> if it has never been called.
*/
public long getLastReplicated() {
return lastReplicated;
}
/**
* Sets the {@link #getLastReplicated() lastReplicated} field to the current
* time.
*/
public void updateLastReplicated() {
lastReplicated = System.currentTimeMillis();
}
/**
* Get the maximum interval between requests, in
* <strong>milliseconds</strong>, after which a request will trigger
* replication of the session's metadata regardless of whether the request
* has otherwise made the session dirty.
* <p/>
* <strong>NOTE:</strong> This value is in milliseconds while the equivalent
* property in JBossCacheManager is in seconds.
*
* @return the maximum interval since last replication after which a request
* will trigger session metadata replication. A value of
* <code>0</code> means replicate metadata on every request; a value
* of <code>-1</code> means never replicate metadata unless the
* session is otherwise dirty.
*/
public long getMaxUnreplicatedInterval() {
return maxUnreplicatedInterval;
}
/**
* Sets the maximum interval between requests, in
* <strong>milliseconds</strong>, after which a request will trigger
* replication of the session's metadata regardless of whether the request
* has otherwise made the session dirty.
* <p/>
* <strong>NOTE:</strong> This value is in milliseconds while the equivalent
* property in JBossCacheManager is in seconds.
*
* @param maxUnreplicatedInterval
* the maximum interval since last replication after which a
* request will trigger session metadata replication. A value of
* <code>0</code> means replicate metadata on every request; a
* value of <code>-1</code> means never replicate metadata unless
* the session is otherwise dirty. A value less than
* <code>-1</code> is treated as <code>-1</code>.
*/
public void setMaxUnreplicatedInterval(long interval) {
this.maxUnreplicatedInterval = Math.max(interval, -1);
checkAlwaysReplicateMetadata();
}
public boolean getExceedsMaxUnreplicatedInterval() {
boolean exceeds = alwaysReplicateMetadata;
if (!exceeds && maxUnreplicatedInterval > 0) // -1 means ignore
{
long unrepl = System.currentTimeMillis() - lastReplicated;
exceeds = (unrepl >= maxUnreplicatedInterval);
}
return exceeds;
}
private void checkAlwaysReplicateMetadata() {
boolean was = this.alwaysReplicateMetadata;
this.alwaysReplicateMetadata = (maxUnreplicatedInterval == 0 || (maxUnreplicatedInterval > 0));
if (this.alwaysReplicateMetadata && !was && logger.isTraceEnabled()) {
logger
.trace(key
+ " will always replicate metadata; maxUnreplicatedInterval="
+ maxUnreplicatedInterval);
}
}
/**
* This is called after loading a session to initialize the transient
* values.
*
* @param context
*/
public abstract void initAfterLoad(JBossCacheSipManager manager);
/**
* Propogate session to the internal store.
*/
public abstract void processSessionRepl();
/**
* Remove myself from the internal store.
*/
public abstract void removeMyself();
/**
* Remove myself from the <t>local</t> internal store.
*/
public abstract void removeMyselfLocal();
// ----------------------------------------------- Overridden Public Methods
public void access() {
super.access();
// JBAS-3528. If it's not the first access, make sure
// the 'new' flag is correct
//SipSession doesn't have the isNew flag
// if (!firstAccess && isNew) {
// setNew(false);
// }
if (invalidationPolicy == WebMetaData.SESSION_INVALIDATE_ACCESS) {
this.sessionMetadataDirty();
}
}
// public void endAccess() {
// super.endAccess();
//
// if (firstAccess) {
// firstAccess = false;
// // Tomcat marks the session as non new, but that's not really
// // accurate per SRV.7.2, as the second request hasn't come in yet
// // So, we fix that
// isNew = true;
// }
// }
public Object getAttribute(String name) {
if (!isValid())
throw new IllegalStateException(sm
.getString("clusteredSession.getAttribute.ise"));
return getAttributeInternal(name);
}
public Iterator<String> getAttributeNames() {
if (!isValid())
throw new IllegalStateException(sm
.getString("clusteredSession.getAttributeNames.ise"));
return getAttributesInternal().keySet().iterator();
}
public void setAttribute(String name, Object value) {
// Name cannot be null
if (name == null)
throw new IllegalArgumentException(sm
.getString("clusteredSession.setAttribute.namenull"));
// Null value is the same as removeAttribute()
if (value == null) {
removeAttribute(name);
return;
}
// Validate our current state
if (!isValid())
throw new IllegalStateException(sm
.getString("clusteredSession.setAttribute.ise"));
if ((sipContext.getSipManager() != null) && sipContext.getSipManager().getDistributable()
&& !(canAttributeBeReplicated(value)))
throw new IllegalArgumentException(sm
.getString("clusteredSession.setAttribute.iae"));
// Construct an event with the new value
SipApplicationSessionBindingEvent event = null;
// Call the valueBound() method if necessary
if (value instanceof SipApplicationSessionBindingListener) {
event = new SipApplicationSessionBindingEvent(this, name);
try {
((SipApplicationSessionBindingListener) value).valueBound(event);
} catch (Throwable t) {
sipContext.getLogger().error(
sm.getString("standardSession.bindingEvent"), t);
}
}
// Replace or add this attribute
Object unbound = setInternalAttribute(name, value);
// Call the valueUnbound() method if necessary
if ((unbound != null) && (unbound != value)
&& (unbound instanceof SipApplicationSessionBindingListener)) {
try {
((SipApplicationSessionBindingListener) unbound)
.valueUnbound(new SipApplicationSessionBindingEvent(this,
name));
} catch (Throwable t) {
sipContext.getLogger().error(
sm.getString("standardSession.bindingEvent"), t);
}
}
// Notify interested application event listeners
List<SipApplicationSessionAttributeListener> listeners = sipContext.getListeners().getSipApplicationSessionAttributeListeners();
if (listeners == null)
return;
for (SipApplicationSessionAttributeListener listener : listeners) {
try {
if (unbound != null) {
// fireContainerEvent(context,
// "beforeSessionAttributeReplaced", listener);
if (event == null) {
event = new SipApplicationSessionBindingEvent(this, name);
}
listener.attributeReplaced(event);
// fireContainerEvent(context,
// "afterSessionAttributeReplaced", listener);
} else {
// fireContainerEvent(context, "beforeSessionAttributeAdded",
// listener);
if (event == null) {
event = new SipApplicationSessionBindingEvent(this, name);
}
listener.attributeAdded(event);
// fireContainerEvent(context, "afterSessionAttributeAdded",
// listener);
}
} catch (Throwable t) {
try {
// if (unbound != null) {
// fireContainerEvent(context,
// "afterSessionAttributeReplaced", listener);
// } else {
// fireContainerEvent(context,
// "afterSessionAttributeAdded", listener);
// }
} catch (Exception e) {
;
}
sipContext.getLogger().error(
sm.getString("standardSession.attributeEvent"), t);
}
}
}
/**
* Returns whether the attribute's type is one that can be replicated.
*
* @param attribute
* the attribute
* @return <code>true</code> if <code>attribute</code> is <code>null</code>,
* <code>Serializable</code> or an array of primitives.
*/
protected boolean canAttributeBeReplicated(Object attribute) {
if (attribute instanceof Serializable || attribute == null)
return true;
Class clazz = attribute.getClass().getComponentType();
return (clazz != null && clazz.isPrimitive());
}
/**
* Invalidates this session and unbinds any objects bound to it. Overridden
* here to remove across the cluster instead of just expiring.
*
* @exception IllegalStateException
* if this method is called on an invalidated session
*/
// public void invalidate() {
// if (!isValid())
// throw new IllegalStateException(sm
// .getString("clusteredSession.invalidate.ise"));
//
// // Cause this session to expire globally
// boolean notify = true;
// boolean localCall = true;
// boolean localOnly = false;
// expire(notify, localCall, localOnly);
// }
/**
* Overrides the {@link StandardSession#isValid() superclass method} to call @
* #isValid(boolean) isValid(true)} .
*/
public boolean isValid() {
return isValid(true);
}
/**
* Returns whether the current session is still valid, but only calls
* {@link #expire(boolean)} for timed-out sessions if
* <code>expireIfInvalid</code> is <code>true</code>.
*
* @param expireIfInvalid
* <code>true</code> if sessions that have been timed out should
* be expired
*/
public boolean isValid(boolean expireIfInvalid) {
// if (this.expiring) {
// return true;
// }
if (!this.isValid) {
return false;
}
// if (ACTIVITY_CHECK && accessCount.get() > 0) {
// return true;
// }
// if (maxInactiveInterval >= 0) {
// long timeNow = System.currentTimeMillis();
// int timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L);
// if (timeIdle >= maxInactiveInterval) {
// if (expireIfInvalid)
// expire(true);
// else
// return false;
// }
// }
return (this.isValid);
}
/**
* Expires the session, but in such a way that other cluster nodes are
* unaware of the expiration.
*
* @param notify
*/
public void expire(boolean notify) {
boolean localCall = true;
boolean localOnly = true;
expire(notify, localCall, localOnly);
}
/**
* Expires the session, notifying listeners and possibly the manager.
* <p>
* <strong>NOTE:</strong> The manager will only be notified of the
* expiration if <code>localCall</code> is <code>true</code>; otherwise it
* is the responsibility of the caller to notify the manager that the
* session is expired. (In the case of JBossCacheManager, it is the manager
* itself that makes such a call, so it of course is aware).
* </p>
*
* @param notify
* whether servlet spec listeners should be notified
* @param localCall
* <code>true</code> if this call originated due to local
* activity (such as a session invalidation in user code or an
* expiration by the local background processing thread);
* <code>false</code> if the expiration originated due to some
* kind of event notification from the cluster.
* @param localOnly
* <code>true</code> if the expiration should not be announced to
* the cluster, <code>false</code> if other cluster nodes should
* be made aware of the expiration. Only meaningful if
* <code>localCall</code> is <code>true</code>.
*/
public void expire(boolean notify, boolean localCall, boolean localOnly) {
if (logger.isDebugEnabled()) {
logger.debug("The session has expired with id: " + key
+ " -- is it local? " + localOnly);
}
// If another thread is already doing this, stop
// if (expiring)
// return;
synchronized (this) {
// If we had a race to this sync block, another thread may
// have already completed expiration. If so, don't do it again
if (!isValid)
return;
if (sipContext.getSipManager() == null)
return;
// expiring = true;
// Notify interested application event listeners
// FIXME - Assumes we call listeners in reverse order
List<SipApplicationSessionListener> listeners = sipContext.getListeners().getSipApplicationSessionListeners();
if (notify && (listeners != null)) {
SipApplicationSessionEvent event = new SipApplicationSessionEvent(this);
for (SipApplicationSessionListener listener : listeners) {
try {
// fireContainerEvent(context, "beforeSessionDestroyed",
// listener);
listener.sessionDestroyed(event);
// fireContainerEvent(context, "afterSessionDestroyed",
// listener);
} catch (Throwable t) {
// try {
// fireContainerEvent(context,
// "afterSessionDestroyed", listener);
// } catch (Exception e) {
// ;
// }
sipContext.getLogger().error(
sm.getString("standardSession.sessionEvent"),t);
}
}
}
// if (ACTIVITY_CHECK) {
// accessCount.set(0);
// }
// Notify interested session event listeners.
// if (notify) {
// fireSessionEvent(Session.SESSION_DESTROYED_EVENT, null);
// }
// JBAS-1360 -- Unbind any objects associated with this session
String keys[] = keys();
for (int i = 0; i < keys.length; i++)
removeAttributeInternal(keys[i], localCall, localOnly, notify);
// Remove this session from our manager's active sessions
removeFromManager(localCall, localOnly);
// We have completed expire of this session
// setValid(false);
// expiring = false;
}
}
/**
* Advise our manager to remove this expired session.
*
* @param localCall
* whether this call originated from local activity or from a
* remote invalidation. In this default implementation, this
* parameter is ignored.
* @param localOnly
* whether the rest of the cluster should be made aware of the
* removal
*/
protected void removeFromManager(boolean localCall, boolean localOnly) {
if (localOnly) {
((JBossCacheSipManager) sipContext.getSipManager()).removeLocal(this);
} else {
sipContext.getSipManager().removeSipApplicationSession(key);
}
}
public void passivate() {
// Notify interested session event listeners
// fireSessionEvent(Session.SESSION_PASSIVATED_EVENT, null);
if (hasActivationListener != Boolean.FALSE) {
boolean hasListener = false;
// Notify ActivationListeners
SipApplicationSessionEvent event = null;
String keys[] = keys();
Map attrs = getAttributesInternal();
for (int i = 0; i < keys.length; i++) {
Object attribute = attrs.get(keys[i]);
if (attribute instanceof SipApplicationSessionActivationListener) {
hasListener = true;
if (event == null)
event = new SipApplicationSessionEvent(this);
try {
((SipApplicationSessionActivationListener) attribute)
.sessionWillPassivate(event);
} catch (Throwable t) {
sipContext.getLogger().error(sm.getString("clusteredSession.attributeEvent"),t);
}
}
}
hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
}
}
public void activate() {
// Notify interested session event listeners
// fireSessionEvent(Session.SESSION_ACTIVATED_EVENT, null);
if (hasActivationListener != Boolean.FALSE) {
// Notify ActivationListeners
boolean hasListener = false;
SipApplicationSessionEvent event = null;
String keys[] = keys();
Map attrs = getAttributesInternal();
for (int i = 0; i < keys.length; i++) {
Object attribute = attrs.get(keys[i]);
if (attribute instanceof SipApplicationSessionActivationListener) {
hasListener = true;
if (event == null)
event = new SipApplicationSessionEvent(this);
try {
((SipApplicationSessionActivationListener) attribute)
.sessionDidActivate(event);
} catch (Throwable t) {
sipContext.getLogger().error(sm.getString("clusteredSession.attributeEvent"),t);
}
}
}
hasActivationListener = hasListener ? Boolean.TRUE : Boolean.FALSE;
}
}
// TODO uncomment when work on JBAS-1900 is completed
// public void removeNote(String name)
// {
// // FormAuthenticator removes the username and password because
// // it assumes they are not needed if the Principal is cached,
// // but they are needed if the session fails over, so ignore
// // the removal request.
// // TODO discuss this on Tomcat dev list to see if a better
// // way of handling this can be found
// if (Constants.SESS_USERNAME_NOTE.equals(name)
// || Constants.SESS_PASSWORD_NOTE.equals(name))
// {
// if (logger.isDebugEnabled())
// {
// logger.debug("removeNote(): ignoring removal of note " + name);
// }
// }
// else
// {
// super.removeNote(name);
// }
//
// }
// TODO uncomment when work on JBAS-1900 is completed
// public void setNote(String name, Object value)
// {
// super.setNote(name, value);
//
// if (Constants.SESS_USERNAME_NOTE.equals(name)
// || Constants.SESS_PASSWORD_NOTE.equals(name))
// {
// sessionIsDirty();
// }
// }
/**
* 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();
//
// // Fields that the superclass isn't clearing
// listeners.clear();
// support = new PropertyChangeSupport(this);
//
// invalidationPolicy = 0;
// outdatedTime = 0;
// outdatedVersion = 0;
// sessionAttributesDirty = false;
// sessionMetadataDirty = false;
// realId = null;
// useJK = false;
// version = 0;
// hasActivationListener = null;
// lastReplicated = 0;
// maxUnreplicatedFactor = 80;
// calcMaxUnreplicatedInterval();
// }
/**
* 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);
// sessionMetadataDirty();
// }
/**
* Overrides the superclass method to also set the {@link #getRealId()
* realId} property.
*/
// public void setId(String id) {
// // Parse the real id first, as super.setId() calls add(),
// // which depends on having the real id
// parseRealId(id);
// super.setId(id);
// }
// /**
// * 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.userPrincipal;
// this.userPrincipal = principal;
//// support.firePropertyChange("principal", oldPrincipal, this.principal);
//
// if ((oldPrincipal != null && !oldPrincipal.equals(principal))
// || (oldPrincipal == null && principal != null))
// sessionMetadataDirty();
//
// }
// public void setNew(boolean isNew) {
// super.setNew(isNew);
// // Don't replicate metadata just 'cause its the second request
// // The only effect of this is if someone besides a request
// // deserializes metadata from the distributed cache, this
// // field may be out of date.
// // If a request accesses the session, the access() call will
// // set isNew=false, so the request will see the correct value
// // sessionMetadataDirty();
// }
// public void setValid(boolean isValid) {
// super.setValid(isValid);
// sessionMetadataDirty();
// }
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("key: " + key)
.append(" lastAccessedTime: " + lastAccessedTime).append(
" version: " + version).append(
" lastOutdated: " + outdatedTime);
return buf.toString();
}
// --------------------------------------------------------- Externalizable
/**
* Reads all non-transient state from the ObjectOutput <i>except the
* attribute map</i>. Subclasses that wish the attribute map to be read
* should override this method and {@link #writeExternal(ObjectOutput)
* writeExternal()}.
*
* <p>
* This method is deliberately public so it can be used to reset the
* internal state of a session object using serialized contents replicated
* from another JVM via JBossCache.
* </p>
*
* @see java.io.Externalizable#readExternal(java.io.ObjectInput)
*/
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
if(logger.isDebugEnabled()) {
logger.debug("reading sip app session from the cache ");
}
synchronized (this) {
// From SipApplicationSessionImpl
key = (SipApplicationSessionKey )in.readObject();
if(logger.isDebugEnabled()) {
logger.debug("reading sip app session from the cache. key = " + key);
}
creationTime = in.readLong();
lastAccessedTime = in.readLong();
// maxInactiveInterval = in.readInt();
// isNew = in.readBoolean();
isValid = in.readBoolean();
lastAccessedTime = in.readLong();
futureExpirationTimeOnPassivation = in.readLong();
if(expirationTimerTask == null && sipContext.getSipApplicationSessionTimeout() > 0) {
expirationTimerTask = new SipApplicationSessionTimerTask();
}
if(expirationTimerTask != null) {
if(logger.isDebugEnabled()) {
logger.debug("Scheduling reactivted sip application session "+ key +" to expire at " + futureExpirationTimeOnPassivation);
}
expirationTime = futureExpirationTimeOnPassivation;
// expirationTimerFuture = (ScheduledFuture<MobicentsSipApplicationSession>) ExecutorServiceWrapper.getInstance().schedule(expirationTimerTask, futureExpirationTimeOnPassivation, TimeUnit.MILLISECONDS);
}
int nbOfSipSessions = in.readInt();
for (int i = 0; i < nbOfSipSessions; i++) {
String sipSessionKeyStringified = in.readUTF();
try {
SipSessionKey sipSessionKey = SessionManagerUtil.parseSipSessionKey(sipSessionKeyStringified);
if(logger.isDebugEnabled()) {
logger.debug("reading sip session from the cached sip appsession . sip session key = " + sipSessionKey);
}
sipSessionsOnPassivation.add(sipSessionKey);
} catch (ParseException e) {
logger.error("Unexpected exception while parsing the sip session key that has been previously passivated " + sipSessionKeyStringified, e);
}
}
int nbOfHttpSessions = in.readInt();
if(nbOfHttpSessions > 0) {
httpSessions = new CopyOnWriteArraySet<String>();
}
for (int i = 0; i < nbOfHttpSessions; i++) {
httpSessionsOnPassivation.add(in.readUTF());
}
//TODO get the persistent servletTimers
// From ClusteredSession
invalidationPolicy = in.readInt();
version = in.readInt();
// Get our id without any jvmRoute appended
// parseRealId(id);StandardSession
// We no longer know if we have an activationListener
hasActivationListener = null;
// Assume deserialization means replication and use thisAccessedTime
// as a proxy for when replication occurred
updateLastReplicated();
checkAlwaysReplicateMetadata();
// If the session has been replicated, any subsequent
// access cannot be the first.
this.firstAccess = false;
sessionMetadataDirty = false;
sessionAttributesDirty = false;
// TODO uncomment when work on JBAS-1900 is completed
// // Session notes -- for FORM auth apps, allow replicated session
// // to be used without requiring a new login
// // We use the superclass set/removeNote calls here to bypass
// // the custom logic we've added
// String username = (String) in.readObject();
// if (username != null)
// {
// super.setNote(Constants.SESS_USERNAME_NOTE, username);
// }
// else
// {
// super.removeNote(Constants.SESS_USERNAME_NOTE);
// }
// String password = (String) in.readObject();
// if (password != null)
// {
// super.setNote(Constants.SESS_PASSWORD_NOTE, password);
// }
// else
// {
// super.removeNote(Constants.SESS_PASSWORD_NOTE);
// }
}
}
/**
* Writes all non-transient state to the ObjectOutput <i>except the
* attribute map</i>. Subclasses that wish the attribute map to be written
* should override this method and append it.
*
* @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
*/
public void writeExternal(ObjectOutput out) throws IOException {
synchronized (this) {
// From SipApplicationSessionImpl
out.writeObject(key);
out.writeLong(creationTime);
out.writeLong(lastAccessedTime);
// out.writeInt(maxInactiveInterval);
// out.writeBoolean(isNew);
out.writeBoolean(isValid);
out.writeLong(lastAccessedTime);
out.writeLong(expirationTime);
out.writeInt(sipSessions.size());
for (String sipSessionKey : sipSessions.keySet()) {
out.writeUTF(sipSessionKey);
}
if(httpSessions != null) {
out.writeInt(httpSessions.size());
for (HttpSession httpSession : getHttpSessions()) {
out.writeUTF(((ClusteredSession)httpSession).getRealId());
}
} else {
out.writeInt(0);
}
// From ClusteredSession
out.writeInt(invalidationPolicy);
out.writeInt(version);
// TODO uncomment when work on JBAS-1900 is completed
// // Session notes -- for FORM auth apps, allow replicated session
// // to be used without requiring a new login
// String username = (String) getNote(Constants.
// SESS_USERNAME_NOTE);
// logger.debug(Constants.SESS_USERNAME_NOTE + " = " + username);
// out.writeObject(username);
// String password = (String) getNote(Constants.SESS_PASSWORD_NOTE);
// logger.debug(Constants.SESS_PASSWORD_NOTE + " = " + password);
// out.writeObject(password);
}
}
// ----------------------------------------------------- Protected Methods
/**
* Removes any attribute whose name is found in {@link #excludedAttributes}
* from <code>attributes</code> and returns a Map of all such attributes.
*
* @param attributes
* source map from which excluded attributes are to be removed.
*
* @return Map that contains any attributes removed from
* <code>attributes</code>, or <code>null</code> if no attributes
* were removed.
*/
protected static Map removeExcludedAttributes(Map attributes) {
Map excluded = null;
for (int i = 0; i < excludedAttributes.length; i++) {
Object attr = attributes.remove(excludedAttributes[i]);
if (attr != null) {
if (logger.isTraceEnabled()) {
logger.trace("Excluding attribute " + excludedAttributes[i]
+ " from replication");
}
if (excluded == null) {
excluded = new HashMap();
}
excluded.put(excludedAttributes[i], attr);
}
}
return excluded;
}
// -------------------------------------- Internal protected method override
/**
* Method inherited from Tomcat. Return zero-length based string if not
* found.
*/
protected String[] keys() {
return ((String[]) getAttributesInternal().keySet()
.toArray(new String[0]));
}
/**
* Called by super.removeAttribute().
*
* @param name
* the attribute name
* @param notify
* <code>true</code> if listeners should be notified
*/
protected void removeAttributeInternal(String name, boolean notify) {
boolean localCall = true;
boolean localOnly = false;
removeAttributeInternal(name, localCall, localOnly, notify);
}
/**
* Remove the attribute from the local cache and possibly the distributed
* cache, plus notify any listeners
*
* @param name
* the attribute name
* @param localCall
* <code>true</code> if this call originated from local activity
* (e.g. a removeAttribute() in the webapp or a local session
* invalidation/expiration), <code>false</code> if it originated
* due to an remote event in the distributed cache.
* @param localOnly
* <code>true</code> if the removal should not be replicated
* around the cluster
* @param notify
* <code>true</code> if listeners should be notified
*/
protected void removeAttributeInternal(String name, boolean localCall,
boolean localOnly, boolean notify) {
// Remove this attribute from our collection
Object value = removeAttributeInternal(name, localCall, localOnly);
// Do we need to do valueUnbound() and attributeRemoved() notification?
if (!notify || (value == null)) {
return;
}
// Call the valueUnbound() method if necessary
SipApplicationSessionBindingEvent event = null;
if (value instanceof SipApplicationSessionBindingListener) {
event = new SipApplicationSessionBindingEvent(this, name);
((SipApplicationSessionBindingListener) value).valueUnbound(event);
}
// Notify interested application event listeners
List<SipApplicationSessionAttributeListener> listeners = sipContext.getListeners().getSipApplicationSessionAttributeListeners();
if (listeners == null)
return;
for (SipApplicationSessionAttributeListener listener : listeners) {
try {
// fireContainerEvent(context, "beforeSessionAttributeRemoved",
// listener);
if (event == null) {
event = new SipApplicationSessionBindingEvent(this, name);
}
listener.attributeRemoved(event);
// fireContainerEvent(context, "afterSessionAttributeRemoved",
// listener);
} catch (Throwable t) {
// try {
// fireContainerEvent(context, "afterSessionAttributeRemoved",
// listener);
// } catch (Exception e) {
// ;
// }
sipContext.getLogger().error(
sm.getString("standardSession.attributeEvent"), t);
}
}
}
/**
* Exists in this class solely to act as an API-compatible bridge to the
* deprecated {@link #removeJBossInternalAttribute(String)}.
* JBossCacheClusteredSession subclasses will override this to call their
* own methods that make use of localCall and localOnly
*
* @param name
* @param localCall
* @param localOnly
* @return
*
* @deprecated will be replaced by removeJBossInternalAttribute(String,
* boolean, boolean)
*/
protected Object removeAttributeInternal(String name, boolean localCall,
boolean localOnly) {
return removeJBossInternalAttribute(name);
}
protected Object getAttributeInternal(String name) {
return getJBossInternalAttribute(name);
}
protected Map getAttributesInternal() {
return getJBossInternalAttributes();
}
protected Object setInternalAttribute(String name, Object value) {
if (value instanceof SipApplicationSessionActivationListener)
hasActivationListener = Boolean.TRUE;
return setJBossInternalAttribute(name, value);
}
// ------------------------------------------ JBoss internal abstract method
protected abstract Object getJBossInternalAttribute(String name);
/**
* @deprecated will be replaced by removeJBossInternalAttribute(String,
* boolean, boolean)
*/
protected abstract Object removeJBossInternalAttribute(String name);
protected abstract Map getJBossInternalAttributes();
protected abstract Object setJBossInternalAttribute(String name,
Object value);
// ------------------------------------------------ Session Package Methods
protected void sessionAttributesDirty() {
sessionAttributesDirty = true;
}
protected boolean getSessionAttributesDirty() {
return sessionAttributesDirty;
}
protected void sessionMetadataDirty() {
sessionMetadataDirty = true;
}
protected boolean getSessionMetadataDirty() {
return sessionMetadataDirty;
}
/**
* Calls {@link #sessionAttributesDirty()} and
* {@link #sessionMetadataDirty()}.
*
* @deprecated use one of the more fine-grained methods.
*/
protected void sessionDirty() {
sessionAttributesDirty();
sessionMetadataDirty();
}
public boolean isSessionDirty() {
return sessionAttributesDirty || sessionMetadataDirty;
}
public boolean getReplicateSessionBody() {
return sessionMetadataDirty || getExceedsMaxUnreplicatedInterval() ||
(maxUnreplicatedInterval == -1 && sessionAttributesDirty);
}
protected boolean isGetDirty(Object attribute) {
boolean result = false;
switch (invalidationPolicy) {
case (WebMetaData.SESSION_INVALIDATE_SET_AND_GET):
result = true;
break;
case (WebMetaData.SESSION_INVALIDATE_SET_AND_NON_PRIMITIVE_GET):
result = isMutable(attribute);
break;
default:
// result is false
}
return result;
}
protected boolean isMutable(Object attribute) {
return attribute != null
&& !(attribute instanceof String || attribute instanceof Number
|| attribute instanceof Character || attribute instanceof Boolean);
}
}