/* * 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.distributedcache.impl.jbc; import java.util.Map; import org.jboss.cache.Fqn; import org.jboss.cache.buddyreplication.BuddyManager; import org.jboss.cache.notifications.annotation.NodeCreated; import org.jboss.cache.notifications.annotation.NodeModified; import org.jboss.cache.notifications.annotation.NodeRemoved; import org.jboss.cache.notifications.event.NodeCreatedEvent; import org.jboss.cache.notifications.event.NodeModifiedEvent; import org.jboss.cache.notifications.event.NodeRemovedEvent; import org.jboss.logging.Logger; import org.jboss.metadata.web.jboss.ReplicationGranularity; import org.jboss.web.tomcat.service.session.ClusteredSipManager; import org.jboss.web.tomcat.service.session.distributedcache.spi.DistributableSessionMetadata; import org.jboss.web.tomcat.service.session.distributedcache.spi.LocalDistributableSessionManager; /** * 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> */ @org.jboss.cache.notifications.annotation.CacheListener public class SipCacheListener extends CacheListenerBase { protected static Logger logger = Logger.getLogger(SipCacheListener.class); // Element within an FQN that is SIPSESSION protected static final int SIPSESSION_FQN_INDEX = 0; // Element within an FQN that is the hostname protected static final int HOSTNAME_FQN_INDEX = 1; // ELEMENT within an FQN this is the sipappname protected static final int SIPAPPNAME_FQN_INDEX = 2; // Element within an FQN that is the sip app session id protected static final int SIPAPPSESSION_ID_FQN_INDEX = 3; // Element within an FQN that is the sip session id protected static final int SIPSESSION_ID_FQN_INDEX = 4; // Size of an Fqn that points to the root of a session protected static final int SIPAPPSESSION_FQN_SIZE = SIPAPPSESSION_ID_FQN_INDEX + 1; // Element within an FQN that is the root of a Pojo attribute map protected 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 protected static final int SIPSESSION_FQN_SIZE = SIPSESSION_ID_FQN_INDEX + 1; // Element within an FQN that is the root of a Pojo attribute map protected 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 protected 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 protected 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 protected 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 protected static final int SIPSESSION_POJO_KEY_FQN_SIZE = SIPSESSION_POJO_KEY_FQN_INDEX + 1; // The index of the root of a buddy backup subtree protected 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) protected static final int BUDDY_BACKUP_ROOT_OWNER_SIZE = BUDDY_BACKUP_ROOT_OWNER_INDEX + 1; // Element within an FQN that is the root of a session's internal pojo storage area protected static final int SIPSESSION_POJO_INTERNAL_FQN_INDEX = SIPSESSION_ID_FQN_INDEX + 1; // Minimum size of an FQN that is below the root of a session's internal pojo storage area protected static final int SIPSESSION_POJO_INTERNAL_FQN_SIZE = SIPSESSION_POJO_INTERNAL_FQN_INDEX + 1; // Element within an FQN that is the root of a session's internal pojo storage area protected static final int SIPAPPSESSION_POJO_INTERNAL_FQN_INDEX = SIPAPPSESSION_ID_FQN_INDEX + 1; // Minimum size of an FQN that is below the root of a session's internal pojo storage area protected static final int SIPAPPSESSION_POJO_INTERNAL_FQN_SIZE = SIPAPPSESSION_POJO_INTERNAL_FQN_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 String sipApplicationName; private boolean fieldBased_; private boolean attributeBased_; SipCacheListener(JBossCacheWrapper wrapper, LocalDistributableSessionManager manager, String contextHostPath, ReplicationGranularity granularity, String sipApplicationName) { super(manager, contextHostPath); if (granularity == ReplicationGranularity.FIELD) fieldBased_ = true; else if (granularity == ReplicationGranularity.ATTRIBUTE) attributeBased_ = true; this.sipApplicationName = sipApplicationName; } protected boolean isFqnForOurSipapp(Fqn<String> fqn, boolean isBuddy) { try { if (sipApplicationName.equals(fqn.get(isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPNAME_FQN_INDEX : SIPAPPNAME_FQN_INDEX)) && AbstractJBossCacheService.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; } // --------------- CacheListener methods ------------------------------------ @NodeCreated public void nodeCreated(NodeCreatedEvent event) { logger.info("following node created " + event.getFqn().toString()); } @NodeRemoved public void nodeRemoved(NodeRemovedEvent event) { if (event.isPre()) return; boolean local = event.isOriginLocal(); if (!fieldBased_ && local) return; @SuppressWarnings("unchecked") Fqn<String> fqn = event.getFqn(); boolean isBuddy = isBuddyFqn(fqn); if (!local && isFqnSessionRootSized(fqn.size(), isBuddy) && isFqnForOurSipapp(fqn, isBuddy)) { // A session has been invalidated from another node; // need to inform manager String sessId = null; if(isFqnSipApplicationSessionRootSized(fqn.size(), isBuddy)) { sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager)manager_).notifyRemoteSipApplicationSessionInvalidation(sessId); } else { sessId = getSipSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager)manager_).notifyRemoteSipSessionInvalidation(sessId); } } else if (local && !isBuddy && (isPossibleSipApplicationSessionInternalPojoFqn(fqn) || isPossibleSipSessionInternalPojoFqn(fqn)) && isFqnForOurWebapp(fqn, isBuddy)) { String sessId = null; if (isFqnSipApplicationSessionRootSized(fqn.size(), isBuddy)) { sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager) manager_) .notifySipApplicationSessionLocalAttributeModification(sessId); } else { sessId = getSipSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager) manager_) .notifySipSessionLocalAttributeModification(sessId); } } } @NodeModified public void nodeModified(NodeModifiedEvent event) { if (event.isPre()) return; boolean local = event.isOriginLocal(); if (!fieldBased_ && local) return; @SuppressWarnings("unchecked") Fqn<String> fqn = event.getFqn(); boolean isBuddy = isBuddyFqn(fqn); if (!local &&isFqnSessionRootSized(fqn.size(), isBuddy) &&isFqnForOurSipapp(fqn, isBuddy)) { // Query if we have version value in the distributed cache. // If we have a version value, compare the version and invalidate if necessary. @SuppressWarnings("unchecked") Map<Object, Object> data = event.getData(); Integer version = (Integer) data.get(AbstractJBossCacheService.VERSION_KEY); if(version != null) { String sipAppSessionId = getSipApplicationSessionIdFromFqn(fqn, isBuddy); String sipSessionId = null; boolean isSipApplicationSession = true; if(!isFqnSipApplicationSessionRootSized(fqn.size(), isBuddy)) { sipSessionId = getSipSessionIdFromFqn(fqn, isBuddy); isSipApplicationSession = false; } String owner = isBuddy ? getBuddyOwner(fqn) : null; Long timestamp = (Long) data.get(AbstractJBossCacheService.TIMESTAMP_KEY); if (timestamp == null) { log_.warn("No timestamp attribute found in " + fqn); } else { boolean updated = false; if(isSipApplicationSession) { // Notify the manager that a session has been updated updated = ((ClusteredSipManager) manager_).sipApplicationSessionChangedInDistributedCache(sipAppSessionId, owner, version.intValue(), timestamp.longValue(), (DistributableSessionMetadata) data.get(AbstractJBossCacheService.METADATA_KEY)); } else { // Notify the manager that a session has been updated updated = ((ClusteredSipManager) manager_).sipSessionChangedInDistributedCache(sipAppSessionId, sipSessionId, owner, version.intValue(), timestamp.longValue(), (DistributableSessionMetadata) data.get(AbstractJBossCacheService.METADATA_KEY)); } if (!updated && !isBuddy) { log_.warn("Possible concurrency problem: Replicated version id " + version + " is less than or equal to in-memory version for session app id " + sipAppSessionId + " and session id " + sipSessionId); } /*else { 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 if (!attributeBased_) // other granularities can modify attributes only { log_.warn("No version attribute found in " + fqn); } } else if (local && !isBuddy && (isPossibleSipApplicationSessionInternalPojoFqn(fqn) || isPossibleSipSessionInternalPojoFqn(fqn)) && isFqnForOurSipapp(fqn, isBuddy)) { // One of our sessions' pojos is modified; need to inform // the manager so it can mark the session dirty String sessId = null; if (isFqnSipApplicationSessionRootSized(fqn.size(), isBuddy)) { sessId = getSipApplicationSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager) manager_) .notifySipApplicationSessionLocalAttributeModification(sessId); } else { sessId = getSipSessionIdFromFqn(fqn, isBuddy); ((ClusteredSipManager) manager_) .notifySipSessionLocalAttributeModification(sessId); } } } protected 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); } protected static boolean isFqnSipApplicationSessionRootSized(int size, boolean isBuddy) { return size == (isBuddy ? BUDDY_BACKUP_ROOT_OWNER_SIZE + SIPAPPSESSION_FQN_SIZE : SIPAPPSESSION_FQN_SIZE); } protected 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); } } protected 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); } } /** * Check if the fqn is big enough to be in the internal pojo area but isn't * in the regular attribute area. * * Structure in the cache is: * * /SIPSESSION * ++ /contextPath_hostname * ++++ /sipApplicationName * ++++++ /id * ++++++++ /sipsessionid * ++++++++++ /ATTRIBUTE * ++++++++++ /_JBossInternal_ * ++++++++++++ etc etc * * If the Fqn size is big enough to be "etc etc" or lower, but the 4th level * is not "ATTRIBUTE", it must be under _JBossInternal_. We discriminate * based on != ATTRIBUTE to avoid having to code to the internal PojoCache * _JBossInternal_ name. * * @param fqn * @return */ public static boolean isPossibleSipSessionInternalPojoFqn(Fqn<String> fqn) { return (fqn.size() > SIPSESSION_POJO_INTERNAL_FQN_SIZE && FieldBasedJBossCacheService.ATTRIBUTE .equals(fqn.get(SIPSESSION_POJO_INTERNAL_FQN_INDEX)) == false); } /** * Check if the fqn is big enough to be in the internal pojo area but isn't * in the regular attribute area. * * Structure in the cache is: * * /SIPSESSION * ++ /contextPath_hostname * ++++ /sipApplicationName * ++++++ /id * ++++++++ /ATTRIBUTE * ++++++++ /_JBossInternal_ * ++++++++++ etc etc * * If the Fqn size is big enough to be "etc etc" or lower, but the 4th level * is not "ATTRIBUTE", it must be under _JBossInternal_. We discriminate * based on != ATTRIBUTE to avoid having to code to the internal PojoCache * _JBossInternal_ name. * * @param fqn * @return */ public static boolean isPossibleSipApplicationSessionInternalPojoFqn(Fqn<String> fqn) { return (fqn.size() > SIPAPPSESSION_POJO_INTERNAL_FQN_SIZE && FieldBasedJBossCacheService.ATTRIBUTE .equals(fqn.get(SIPAPPSESSION_POJO_INTERNAL_FQN_INDEX)) == false); } }