/* * 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; // } }