/* * Copyright 2006-2010 Daniel Henninger. All rights reserved. * * This software is published under the terms of the GNU Public License (GPL), * a copy of which is included in this distribution. */ package net.sf.kraken.session.cluster; import net.sf.kraken.BaseTransport; import net.sf.kraken.KrakenPlugin; import net.sf.kraken.TransportInstance; import net.sf.kraken.roster.TransportBuddy; import net.sf.kraken.session.TransportSession; import org.apache.log4j.Logger; import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.cluster.ClusterEventListener; import org.jivesoftware.openfire.cluster.ClusterManager; import org.jivesoftware.openfire.session.ClientSession; import org.jivesoftware.util.cache.Cache; import org.jivesoftware.util.cache.CacheFactory; import org.xmpp.packet.JID; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.concurrent.locks.Lock; /** * Keeps track of location of sessions across cluster. * * @author Daniel Henninger */ public class TransportSessionRouter implements ClusterEventListener { static Logger Log = Logger.getLogger(TransportSessionRouter.class); public static final String TRANSPORTSESSION_CACHE_NAME = "Kraken Session Location Cache"; /** * Cache (unlimited, never expire) that holds the locations of a transport session. * Key: transport type (aim, icq, etc) + bare JID, Value: nodeID * We store the key like BareJID@transportType so... user@example.org@msn */ public Cache<String, byte[]> sessionLocations; /** * The instance of the kraken plugin we are attached to. */ private WeakReference<KrakenPlugin> pluginRef; /** * Creates a transport session router instance associated with the plugin. * * @param plugin Kraken plugin instance we are associated with. */ public TransportSessionRouter(KrakenPlugin plugin) { pluginRef = new WeakReference<KrakenPlugin>(plugin); sessionLocations = CacheFactory.createCache(TRANSPORTSESSION_CACHE_NAME); ClusterManager.addListener(this); } /** * Shuts down the transport session router. */ public void shutdown() { ClusterManager.removeListener(this); } /** * Retrieves the reference to the plugin we are associated with. * * @return KrakenPlugin instance. */ public KrakenPlugin getPlugin() { return pluginRef.get(); } /** * Adds information about a session to the cache. * * @param transportType Type of transport the session is associated with. * @param jid Bare JID of session owner. */ public void addSession(String transportType, String jid) { sessionLocations.put(jid+"@"+transportType, XMPPServer.getInstance().getNodeID().toByteArray()); } /** * Returns the node id of a session. * * @param transportType Type of transport the session is associated with. * @param jid Bare JID of session owner. * @return Node ID that is handling */ public byte[] getSession(String transportType, String jid) { if (!sessionLocations.containsKey(jid+"@"+transportType)) { return null; } return sessionLocations.get(jid+"@"+transportType); } /** * Removes information about a session from the cache. * * @param transportType Type of transport the session is associated with. * @param jid Base JID of session owner. */ public void removeSession(String transportType, String jid) { sessionLocations.remove(jid+"@"+transportType); } /** * @see org.jivesoftware.openfire.cluster.ClusterEventListener#joinedCluster() */ public void joinedCluster() { restoreCacheContent(); } /** * @see org.jivesoftware.openfire.cluster.ClusterEventListener#joinedCluster(byte[]) */ public void joinedCluster(byte[] joiningNodeID) { // Do nothing } /** * @see org.jivesoftware.openfire.cluster.ClusterEventListener#leftCluster() */ public void leftCluster() { restoreCacheContent(); } /** * @see org.jivesoftware.openfire.cluster.ClusterEventListener#leftCluster(byte[]) */ public void leftCluster(byte[] leavingNodeID) { KrakenPlugin plugin = getPlugin(); // TODO: Is this correct? Lets say another node updates an entry before I get to it, will I see the update? for (Map.Entry<String,byte[]> entry : sessionLocations.entrySet()) { if (Arrays.equals(entry.getValue(), leavingNodeID)) { Lock l = CacheFactory.getLock(entry.getKey()+"lc", sessionLocations); try { l.lock(); String jid = entry.getKey().substring(0, entry.getKey().lastIndexOf("@")); String trType = entry.getKey().substring(entry.getKey().lastIndexOf("@")+1); Log.debug("Kraken: Node handling session "+jid+" on "+trType+" lost, taking over session..."); sessionLocations.remove(jid+"@"+trType); TransportInstance trInstance = plugin.getTransportInstance(trType); if (trInstance != null) { BaseTransport<? extends TransportBuddy> transport = trInstance.getTransport(); if (transport != null) { Collection<ClientSession> sessions = XMPPServer.getInstance().getSessionManager().getSessions(new JID(jid).getNode()); for (ClientSession session : sessions) { transport.processPacket(session.getPresence()); } } } } finally { l.unlock(); } } } } /** * @see org.jivesoftware.openfire.cluster.ClusterEventListener#markedAsSeniorClusterMember() */ public void markedAsSeniorClusterMember() { // Do nothing } /** * Populates the cache with our sessions. */ private void restoreCacheContent() { for (String transportName : getPlugin().getTransports()) { if (getPlugin().serviceEnabled(transportName)) { TransportInstance ti = getPlugin().getTransportInstance(transportName); if (ti != null) { BaseTransport<? extends TransportBuddy> tr = ti.getTransport(); if (tr != null) { for (TransportSession<? extends TransportBuddy> session : tr.getSessionManager().getSessions()) { addSession(transportName, session.getJID().toBareJID()); } } } } } } }