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