/*
* JBoss, Home of Professional Open Source.
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.web.tomcat.service.session;
import java.text.ParseException;
import org.jboss.cache.Fqn;
import org.jboss.cache.buddyreplication.BuddyManager;
import org.jboss.logging.Logger;
import org.jboss.metadata.WebMetaData;
import org.mobicents.servlet.sip.core.session.SessionManagerUtil;
import org.mobicents.servlet.sip.core.session.SipApplicationSessionKey;
/**
* Listens for distributed caches events, notifying the JBossCacheManager
* of events of interest.
*
* @author Brian Stansberry
* @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A>
*/
public class SipCacheListener extends AbstractCacheListener
{
protected static Logger logger = Logger.getLogger(SipCacheListener.class);
// Element within an FQN that is SIPSESSION
private static final int SIPSESSION_FQN_INDEX = 0;
// Element within an FQN that is the hostname
private static final int HOSTNAME_FQN_INDEX = 1;
// ELEMENT within an FQN this is the sipappname
private static final int SIPAPPNAME_FQN_INDEX = 2;
// Element within an FQN that is the sip app session id
private static final int SIPAPPSESSION_ID_FQN_INDEX = 3;
// Element within an FQN that is the sip session id
private static final int SIPSESSION_ID_FQN_INDEX = 4;
// Size of an Fqn that points to the root of a session
private static final int SIPAPPSESSION_FQN_SIZE = SIPAPPSESSION_ID_FQN_INDEX + 1;
// Element within an FQN that is the root of a Pojo attribute map
private static final int SIPAPPSESSION_POJO_ATTRIBUTE_FQN_INDEX = SIPAPPSESSION_ID_FQN_INDEX + 1;
// Size of an Fqn that points to the root of a session
private static final int SIPSESSION_FQN_SIZE = SIPSESSION_ID_FQN_INDEX + 1;
// Element within an FQN that is the root of a Pojo attribute map
private static final int SIPSESSION_POJO_ATTRIBUTE_FQN_INDEX = SIPSESSION_ID_FQN_INDEX + 1;
// Element within an FQN that is the root of an individual Pojo attribute
private static final int SIPAPPSESSION_POJO_KEY_FQN_INDEX = SIPAPPSESSION_POJO_ATTRIBUTE_FQN_INDEX + 1;
// Size of an Fqn that points to the root of a session
private static final int SIPAPPSESSION_POJO_KEY_FQN_SIZE = SIPAPPSESSION_POJO_KEY_FQN_INDEX + 1;
// Element within an FQN that is the root of an individual Pojo attribute
private static final int SIPSESSION_POJO_KEY_FQN_INDEX = SIPSESSION_POJO_ATTRIBUTE_FQN_INDEX + 1;
// Size of an Fqn that points to the root of a session
private static final int SIPSESSION_POJO_KEY_FQN_SIZE = SIPSESSION_POJO_KEY_FQN_INDEX + 1;
// The index of the root of a buddy backup subtree
private static final int BUDDY_BACKUP_ROOT_OWNER_INDEX = BuddyManager.BUDDY_BACKUP_SUBTREE_FQN.size();
// The size of the root of a buddy backup subtree (including owner)
private static final int BUDDY_BACKUP_ROOT_OWNER_SIZE = BUDDY_BACKUP_ROOT_OWNER_INDEX + 1;
// private static final String TREE_CACHE_CLASS = "org.jboss.cache.TreeCache";
// private static final String DATA_GRAVITATION_CLEANUP = "_dataGravitationCleanup";
private static Logger log_ = Logger.getLogger(SipCacheListener.class);
private JBossCacheWrapper cacheWrapper_;
private JBossCacheSipManager manager_;
private String sipApplicationName;
private String hostname_;
private boolean fieldBased_;
// When trying to ignore unwanted notifications, do we check for local activity first?
private boolean disdainLocalActivity_;
SipCacheListener(JBossCacheWrapper wrapper, JBossCacheSipManager manager, String hostname, String sipApplicationName)
{
cacheWrapper_ = wrapper;
manager_ = manager;
hostname_ = hostname;
this.sipApplicationName = sipApplicationName;
int granularity = manager_.getReplicationGranularity();
fieldBased_ = (granularity == WebMetaData.REPLICATION_GRANULARITY_FIELD);
// TODO decide if disdaining local activity is always good for REPL_ASYNC
disdainLocalActivity_ = (granularity == WebMetaData.REPLICATION_GRANULARITY_SESSION); // for now
}
// --------------- TreeCacheListener methods ------------------------------------
@Override
public void nodeCreated(Fqn fqn)
{
logger.info("following node created " + fqn.toString() + " with name " +fqn.getName());
}
public void nodeRemoved(Fqn fqn)
{
logger.info("following node removed " + fqn.toString() + " with name " +fqn.getName());
// Ignore our own activity if not field based
boolean local = ConvergedSessionReplicationContext.isSipLocallyActive();
if (!fieldBased_ && local)
return;
boolean isBuddy = isBuddyFqn(fqn);
int size = fqn.size();
if(isFqnSessionRootSized(size, isBuddy))
{
if (!local && isFqnForOurSipapp(fqn, isBuddy))
{
// A session has been invalidated from another node;
// need to inform manager
String sessId = null;
if(isFqnSipApplicationSessionRootSized(size, isBuddy)) {
sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy);
manager_.processRemoteSipApplicationSessionInvalidation(sessId);
} else {
sessId = getSipSessionIdFromFqn(fqn, isBuddy);
manager_.processRemoteSipSessionInvalidation(sessId);
}
}
}
else if (fieldBased_ && isFqnForOurSipapp(fqn, isBuddy))
{
// Potential removal of a Pojo where we need to unregister as an Observer.
if (!local && isFqnPojoKeySized(size, isBuddy))
{
String sessId = null;
String attrKey = null;
if(isFqnSipApplicationSessionRootSized(size, isBuddy)) {
sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy);
attrKey = getSipApplicationSessionIdPojoKeyFromFqn(fqn, isBuddy);
manager_.processRemoteSipApplicationSessionAttributeRemoval(sessId, attrKey);
} else {
sessId = getSipSessionIdFromFqn(fqn, isBuddy);
attrKey = getSipSessionIdPojoKeyFromFqn(fqn, isBuddy);
manager_.processRemoteSipSessionAttributeRemoval(sessId, attrKey);
}
}
else if (local && isFqnInPojo(size, isBuddy))
{
// One of our pojo's is modified
String sessId = null;
if(isFqnSipApplicationSessionRootSized(size, isBuddy)) {
sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy);
manager_.processSipApplicationSessionLocalPojoModification(sessId);
} else {
sessId = getSipSessionIdFromFqn(fqn, isBuddy);
manager_.processSipSessionLocalPojoModification(sessId);
}
}
}
}
public void nodeModified(Fqn fqn)
{
logger.info("following node modified " + fqn.toString() + " with name " +fqn.getName());
boolean local = ConvergedSessionReplicationContext.isSipLocallyActive();
if (!fieldBased_ && local)
return;
boolean isBuddy = isBuddyFqn(fqn);
int size = fqn.size();
// We only care if this is for a session root or it's for a pojo
if (isFqnSessionRootSized(size, isBuddy))
{
if (!local && isFqnForOurSipapp(fqn, isBuddy))
{
handleSessionRootModification(fqn, isBuddy);
}
}
else if (fieldBased_
&& local
&& isFqnForOurSipapp(fqn, isBuddy)
&& isFqnInPojo(size, isBuddy))
{
// One of our pojo's is modified
String sessId = null;
if(isFqnSipApplicationSessionRootSized(size, isBuddy)) {
sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy);
manager_.processSipApplicationSessionLocalPojoModification(sessId);
} else {
sessId = getSipSessionIdFromFqn(fqn, isBuddy);
manager_.processSipSessionLocalPojoModification(sessId);
}
}
}
private void handleSessionRootModification(Fqn fqn, boolean isBuddy)
{
// We only care if this is for our webapp
if (!isFqnForOurSipapp(fqn, isBuddy))
return;
// Query if we have version value in the distributed cache.
// If we have a version value, compare the version and invalidate if necessary.
Integer version = (Integer)cacheWrapper_.get(fqn, JBossCacheService.VERSION_KEY);
if(version != null)
{
String sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy);
boolean isSipApplicationSession = true;
if(!isFqnSipApplicationSessionRootSized(fqn.size(), isBuddy)) {
isSipApplicationSession = false;
}
SipApplicationSessionKey key;
try {
key = SessionManagerUtil.parseSipApplicationSessionKey(sessId);
ClusteredSipApplicationSession sipApplicationSession = manager_.findLocalSipApplicationSession(key, false);
if (sipApplicationSession == null)
{
String owner = isBuddy ? getBuddyOwner(fqn) : null;
// Notify the manager that an unloaded session has been updated
manager_.unloadedSessionChanged(sessId, owner);
}
else if (sipApplicationSession.isNewData(version.intValue()))
{
// Need to invalidate the loaded session
sipApplicationSession.setOutdatedVersion(version.intValue());
if(log_.isTraceEnabled())
{
log_.trace("nodeDirty(): session in-memory data is " +
"invalidated with id: " + sessId + " and version: " +
version.intValue());
}
}
else if (isBuddy)
{
// We have a local session but got a modification for the buddy tree.
// This means another node is in the process of taking over the session;
// we don't worry about it
;
}
else
{
// This could be an issue but can happen legitimately in unusual
// circumstances, so just log something at INFO, not WARN
// Unusual circumstance: create session; don't touch session again
// until timeout period expired; fail over to another node after
// timeout but before session expiration thread has run. Existing
// session will be expired locally on new node and a new session created.
// When that session replicates, the version id will match the still
// existing cached session on the first node. Unlikely, but due
// to design of a unit test, it happens every testsuite run :-)
log_.info("Possible concurrency problem: Replicated version id " +
version + " matches in-memory version for session " + sessId);
// Mark the loaded session outdated anyway; in the above mentioned
// "unusual circumstance" that's the correct thing to do
sipApplicationSession.setOutdatedVersion(version.intValue());
}
if(!isSipApplicationSession) {
sessId = getSipSessionIdFromFqn(fqn, isBuddy);
ClusteredSipSession session = null;
try {
session = manager_.findLocalSipSession(SessionManagerUtil.parseSipSessionKey(sessId), false, sipApplicationSession);
if (session == null)
{
String owner = isBuddy ? getBuddyOwner(fqn) : null;
// Notify the manager that an unloaded session has been updated
manager_.unloadedSipSessionChanged(sessId, owner);
}
else if (session.isNewData(version.intValue()))
{
// Need to invalidate the loaded session
session.setOutdatedVersion(version.intValue());
if(log_.isTraceEnabled())
{
log_.trace("nodeDirty(): session in-memory data is " +
"invalidated with id: " + sessId + " and version: " +
version.intValue());
}
}
else if (isBuddy)
{
// We have a local session but got a modification for the buddy tree.
// This means another node is in the process of taking over the session;
// we don't worry about it
;
}
else
{
// This could be an issue but can happen legitimately in unusual
// circumstances, so just log something at INFO, not WARN
// Unusual circumstance: create session; don't touch session again
// until timeout period expired; fail over to another node after
// timeout but before session expiration thread has run. Existing
// session will be expired locally on new node and a new session created.
// When that session replicates, the version id will match the still
// existing cached session on the first node. Unlikely, but due
// to design of a unit test, it happens every testsuite run :-)
log_.info("Possible concurrency problem: Replicated version id " +
version + " matches in-memory version for session " + sessId);
// Mark the loaded session outdated anyway; in the above mentioned
// "unusual circumstance" that's the correct thing to do
session.setOutdatedVersion(version.intValue());
}
} catch (ParseException e) {
logger.error("An unexpected exception happened while parsing the sip session id : "+ sessId, e);
}
}
} catch (ParseException pe) {
logger.error("An unexpected exception happened while parsing the sip app session id : "+ sessId, pe);
}
}
else
{
log_.warn("No VERSION_KEY attribute found in " + fqn);
}
}
private boolean isFqnForOurSipapp(Fqn fqn, boolean isBuddy)
{
try
{
if (sipApplicationName.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPNAME_FQN_INDEX : SIPAPPNAME_FQN_INDEX))
&& hostname_.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + HOSTNAME_FQN_INDEX : HOSTNAME_FQN_INDEX))
&& JBossCacheService.SESSION.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_FQN_INDEX : SIPSESSION_FQN_INDEX)))
return true;
}
catch (IndexOutOfBoundsException e)
{
// can't be ours; too small; just fall through
}
return false;
}
private static boolean isFqnSessionRootSized(int size, boolean isBuddy)
{
return size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_FQN_SIZE : SIPAPPSESSION_FQN_SIZE) ||
size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_FQN_SIZE : SIPSESSION_FQN_SIZE);
}
private static boolean isFqnPojoKeySized(int size, boolean isBuddy)
{
return size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_SIZE : SIPAPPSESSION_POJO_KEY_FQN_SIZE) ||
size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_POJO_KEY_FQN_SIZE : SIPSESSION_POJO_KEY_FQN_SIZE);
}
private static boolean isFqnSipApplicationSessionRootSized(int size, boolean isBuddy)
{
return size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_FQN_SIZE : SIPAPPSESSION_FQN_SIZE);
}
private static boolean isFqnSipApplicationPojoKeySized(int size, boolean isBuddy)
{
return size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_SIZE : SIPAPPSESSION_POJO_KEY_FQN_SIZE);
}
private static boolean isFqnInPojo(int size, boolean isBuddy)
{
return size >= (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_SIZE : SIPAPPSESSION_POJO_KEY_FQN_SIZE) ||
size >= (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_POJO_KEY_FQN_SIZE : SIPSESSION_POJO_KEY_FQN_SIZE);
}
private static String getSipApplicationSessionIdFromFqn(Fqn fqn, boolean isBuddy)
{
if(isBuddy) {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_ID_FQN_INDEX);
} else {
return (String)fqn.get(SIPAPPSESSION_ID_FQN_INDEX);
}
}
private static String getSipSessionIdFromFqn(Fqn fqn, boolean isBuddy)
{
if(isBuddy) {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_ID_FQN_INDEX);
} else {
return (String)fqn.get(SIPSESSION_ID_FQN_INDEX);
}
}
private static String getSipApplicationSessionIdPojoKeyFromFqn(Fqn fqn, boolean isBuddy)
{
if(isBuddy) {
if(fqn.size() == BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_SIZE) {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_INDEX);
} else {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_POJO_KEY_FQN_INDEX);
}
} else {
if(fqn.size() == SIPAPPSESSION_POJO_KEY_FQN_SIZE) {
return (String)fqn.get(SIPAPPSESSION_POJO_KEY_FQN_INDEX);
} else {
return (String)fqn.get(SIPSESSION_POJO_KEY_FQN_INDEX);
}
}
}
private static String getSipSessionIdPojoKeyFromFqn(Fqn fqn, boolean isBuddy)
{
if(isBuddy) {
if(fqn.size() == BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_SIZE) {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_POJO_KEY_FQN_INDEX);
} else {
return (String)fqn.get(BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPSESSION_POJO_KEY_FQN_INDEX);
}
} else {
if(fqn.size() == SIPAPPSESSION_POJO_KEY_FQN_SIZE) {
return (String)fqn.get(SIPAPPSESSION_POJO_KEY_FQN_INDEX);
} else {
return (String)fqn.get(SIPSESSION_POJO_KEY_FQN_INDEX);
}
}
}
private static boolean isBuddyFqn(Fqn fqn)
{
try
{
return BuddyManager.BUDDY_BACKUP_SUBTREE.equals(fqn.get(0));
}
catch (IndexOutOfBoundsException e)
{
// Can only happen if fqn is ROOT, and we shouldn't get
// notifications for ROOT.
// If it does, just means it's not a buddy
return false;
}
}
/**
* Extracts the owner portion of an buddy subtree Fqn.
*
* @param fqn An Fqn that is a child of the buddy backup root node.
*/
private static String getBuddyOwner(Fqn fqn)
{
return (String) fqn.get(BUDDY_BACKUP_ROOT_OWNER_INDEX);
}
// /**
// * FIXME This is a hack that examines the stack trace looking
// * for the TreeCache._dataGravitationCleanup method.
// *
// * @return
// */
// private static boolean isDataGravitationCleanup()
// {
// StackTraceElement[] trace = new Throwable().getStackTrace();
// for (int i = 0; i < trace.length; i++)
// {
// if (TREE_CACHE_CLASS.equals(trace[i].getClassName())
// && DATA_GRAVITATION_CLEANUP.equals(trace[i].getMethodName()))
// return true;
// }
//
// return false;
// }
}