/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev$
* Last modified by $Author$
* $Date$
*/
package tigase.cluster;
import tigase.cluster.api.ClusterCommandException;
import tigase.cluster.api.ClusterControllerIfc;
import tigase.cluster.api.ClusteredComponentIfc;
import tigase.cluster.api.CommandListener;
import tigase.cluster.strategy.ClusteringStrategyIfc;
import tigase.cluster.strategy.ConnectionRecord;
import tigase.server.Command;
import tigase.server.Message;
import tigase.server.Packet;
import tigase.server.xmppsession.SessionManager;
import tigase.stats.StatisticsList;
import tigase.util.DNSResolver;
import tigase.util.TigaseStringprepException;
import tigase.xml.Element;
import tigase.xmpp.BareJID;
import tigase.xmpp.JID;
import tigase.xmpp.NoConnectionIdException;
import tigase.xmpp.NotAuthorizedException;
import tigase.xmpp.StanzaType;
import tigase.xmpp.XMPPResourceConnection;
import tigase.xmpp.XMPPSession;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.script.Bindings;
/**
* Class SessionManagerClusteredOld
*
*
* Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class SessionManagerClustered extends SessionManager implements
ClusteredComponentIfc {
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger(SessionManagerClustered.class
.getName());
private static final String USER_CONNECTED_CMD = "user-connected-sm-cmd";
private static final String USER_DISCONNECTED_CMD = "user-disconnected-sm-cmd";
private static final String USER_PRESENCE_CMD = "user-presence-sm-cmd";
private static final String PACKET_FORWARD_CMD = "packet-forward-sm-cmd";
private static final String REQUEST_SYNCONLINE_CMD = "req-sync-online-sm-cmd";
private static final String RESPOND_SYNCONLINE_CMD = "resp-sync-online-sm-cmd";
private static final String AUTH_TIME = "auth-time";
public static final String CLUSTER_STRATEGY_VAR = "clusterStrategy";
private static final String PRESENCE_ELEMENT_NAME = "presence";
/** Field description */
public static final String CONNECTION_ID = "connectionId";
/** Field description */
public static final String MY_DOMAIN_NAME_PROP_KEY = "domain-name";
/** Field description */
public static final String RESOURCE = "resource";
/** Field description */
public static final String SM_ID = "smId";
/** Field description */
public static final String STRATEGY_CLASS_PROPERTY = "--sm-cluster-strategy-class";
/** Field description */
public static final String STRATEGY_CLASS_PROP_KEY = "sm-cluster-strategy-class";
/** Field description */
public static final String STRATEGY_CLASS_PROP_VAL =
"tigase.cluster.strategy.SMNonCachingAllNodes";
/** Field description */
public static final int SYNC_MAX_BATCH_SIZE = 1000;
/** Field description */
public static final String USER_ID = "userId";
/** Field description */
public static final String XMPP_SESSION_ID = "xmppSessionId";
private static final String SESSION_FOUND_KEY = "user-session-found-key";
private static final String INITIAL_PRESENCE_KEY = "cluster-initial-presence";
private static final String PRESENCE_TYPE_KEY = "presence-type";
private static final String PRESENCE_TYPE_INITIAL = "initial";
private static final String PRESENCE_TYPE_UPDATE = "update";
private long clusterSyncInTraffic = 0;
private long clusterSyncOutTraffic = 0;
private JID my_address = null;
private JID my_hostname = null;
private int nodesNo = 0;
private ClusteringStrategyIfc strategy = null;
private ClusterControllerIfc clusterController = null;
private CommandListener userConnected = new UserConnectedCommand();
private CommandListener userDisconnected = new UserDisconnectedCommand();
private CommandListener userPresence = new UserPresenceCommand();
private CommandListener packetForward = new PacketForwardCommand();
private CommandListener respondSyncOnline = new RespondSyncOnlineCommand();
private CommandListener requestSyncOnline = new RequestSyncOnlineCommand();
/**
* The method checks whether the given JID is known to the installation,
* either user connected to local machine or any of the cluster nodes. False
* result does not mean the user is not connected. It means the method does
* not know anything about the JID. Some clustering strategies may not cache
* online users' information.
*
* @param jid
* a user's JID for whom we query information.
*
* @return true if the user is known as online to the installation, false if
* the method does not know.
*/
@Override
public boolean containsJid(BareJID jid) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for jid: {0}", jid);
}
return super.containsJid(jid) || strategy.containsJid(jid);
}
/**
* If the installation knows about user's JID, that he is connected to the
* system, then this method returns all user's connection IDs. As an
* optimization we can forward packets to all user's connections directly from
* a single node.
*
* @param jid
* a user's JID for whom we query information.
*
* @return a list of all user's connection IDs.
*/
@Override
public JID[] getConnectionIdsForJid(BareJID jid) {
JID[] ids = super.getConnectionIdsForJid(jid);
if (ids == null) {
ids = strategy.getConnectionIdsForJid(jid);
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for jid: {0}, results: {1}", new Object[] { jid,
Arrays.toString(ids) });
}
return ids;
}
/**
* Loads the component's default configuration to the configuration management
* subsystem.
*
* @param params
* is a Map with system-wide default settings found in
* init.properties file or similar location.
*
* @return a Map with all default component settings generated from the
* default parameters in init.properties file.
*/
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
Map<String, Object> props = super.getDefaults(params);
String strategy_class = (String) params.get(STRATEGY_CLASS_PROPERTY);
if (strategy_class == null) {
strategy_class = STRATEGY_CLASS_PROP_VAL;
}
props.put(STRATEGY_CLASS_PROP_KEY, strategy_class);
try {
ClusteringStrategyIfc strat_tmp =
(ClusteringStrategyIfc) Class.forName(strategy_class).newInstance();
Map<String, Object> strat_defs = strat_tmp.getDefaults(params);
if (strat_defs != null) {
props.putAll(strat_defs);
}
} catch (Exception e) {
log.log(Level.SEVERE, "Can not instantiate clustering strategy for class: "
+ strategy_class, e);
}
String[] local_domains = DNSResolver.getDefHostNames();
if (params.get(GEN_VIRT_HOSTS) != null) {
local_domains = ((String) params.get(GEN_VIRT_HOSTS)).split(",");
}
// defs.put(LOCAL_DOMAINS_PROP_KEY, LOCAL_DOMAINS_PROP_VAL);
props.put(MY_DOMAIN_NAME_PROP_KEY, local_domains[0]);
if (params.get(CLUSTER_NODES) != null) {
String[] cl_nodes = ((String) params.get(CLUSTER_NODES)).split(",");
nodesNo = cl_nodes.length;
}
return props;
}
// private long calcAverage(long[] timings) {
// long res = 0;
//
// for (long ppt : timings) {
// res += ppt;
// }
//
// long processingTime = res / timings.length;
// return processingTime;
// }
/**
* Method generates and returns component's statistics.
*
* @param list
* is a collection with statistics to which this component can add
* own metrics.
*/
@Override
public void getStatistics(StatisticsList list) {
super.getStatistics(list);
strategy.getStatistics(list);
list.add(getName(), "Cluster sync IN traffic", clusterSyncInTraffic, Level.FINE);
list.add(getName(), "Cluster sync OUT traffic", clusterSyncOutTraffic, Level.FINE);
// list.add(getName(), "Average commandTime on last " + commandTime.length
// + " runs [ms]", calcAverage(commandTime), Level.FINE);
// list.add(getName(), "Average clusterTime on last " + clusterTime.length
// + " runs [ms]", calcAverage(clusterTime), Level.FINE);
// list.add(getName(), "Average checkingTime on last " + checkingTime.length
// + " runs [ms]", calcAverage(checkingTime), Level.FINE);
// list.add(getName(), "Average smTime on last " + smTime.length +
// " runs [ms]",
// calcAverage(smTime), Level.FINE);
}
/**
* Returns active clustering strategy object.
*
* @return active clustering strategy object.
*/
public ClusteringStrategyIfc getStrategy() {
return strategy;
}
@Override
public void initBindings(Bindings binds) {
super.initBindings(binds);
binds.put(CLUSTER_STRATEGY_VAR, strategy);
}
// @Override
// public void handleLogout(BareJID userId, XMPPResourceConnection conn) {
// try {
// if (conn.isAuthorized() && conn.isResourceSet()) {
// Map<String, String> params = prepareConnectionParams(conn);
// List<JID> cl_nodes = strategy.getNodesForUserDisconnect(conn.getJID());
// ++clusterSyncOutTraffic;
// clusterController.sendToNodes(USER_DISCONNECTED_CMD, params, getComponentId(),
// cl_nodes.toArray(new JID[cl_nodes.size()]));
// }
// } catch (Exception ex) {
// log.log(Level.WARNING, "This should not happen, check it out!, ", ex);
// }
// super.handleLogout(userId, conn);
// }
/**
* Method intercepts presence set event generated by presence status received
* from a user connected to this node. The presence is then broadcasted to all
* nodes given by the strategy.
*
* @param conn
* a user's XMPPResourceConnection on which the event occurred.
*/
@Override
public void handlePresenceSet(XMPPResourceConnection conn) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for conn: {0}", new Object[] { conn });
}
super.handlePresenceSet(conn);
try {
boolean initPresence = conn.getSessionData(INITIAL_PRESENCE_KEY) == null;
Map<String, String> params = prepareConnectionParams(conn);
if (initPresence) {
conn.putSessionData(INITIAL_PRESENCE_KEY, INITIAL_PRESENCE_KEY);
params.put(PRESENCE_TYPE_KEY, PRESENCE_TYPE_INITIAL);
} else {
params.put(PRESENCE_TYPE_KEY, PRESENCE_TYPE_UPDATE);
}
Element presence = conn.getPresence();
List<JID> cl_nodes =
strategy.getNodesForPacketForward(getComponentId(), null,
Packet.packetInstance(presence));
if (cl_nodes != null && cl_nodes.size() > 0) {
++clusterSyncOutTraffic;
clusterController.sendToNodes(USER_PRESENCE_CMD, params, presence,
getComponentId(), null, cl_nodes.toArray(new JID[cl_nodes.size()]));
}
} catch (Exception e) {
log.log(Level.WARNING, "Problem with broadcast user presence for: " + conn, e);
}
}
/**
* Method intercepts resource bind event generated for on user's connection.
* This event means that the account authentication process has been
* successfully completed and now the information about the event can be
* distributed to all nodes given by the strategy.
*
* @param conn
* a user's XMPPResourceConnection on which the event occurred.
*/
@Override
public void handleResourceBind(XMPPResourceConnection conn) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for conn: {0}", new Object[] { conn });
}
super.handleResourceBind(conn);
try {
Map<String, String> params = prepareConnectionParams(conn);
List<JID> cl_nodes = strategy.getNodesForUserConnect(conn.getJID());
++clusterSyncOutTraffic;
clusterController.sendToNodes(USER_CONNECTED_CMD, params, getComponentId(),
cl_nodes.toArray(new JID[cl_nodes.size()]));
} catch (Exception e) {
log.log(Level.WARNING, "Problem with broadcast user presence for: " + conn, e);
}
}
/**
* Method checks whether the clustering strategy has a complete JIDs info.
* That is whether the strategy knows about all users connected to all nodes.
* Some strategies may choose not to share this information among nodes, hence
* the methods returns false. Other may synchronize this information and can
* provide it to further optimize cluster traffic.
*
*
* @return a true boolean value if the strategy has a complete information
* about all users connected to all cluster nodes.
*/
@Override
public boolean hasCompleteJidsInfo() {
return strategy.hasCompleteJidsInfo();
}
/**
* The method is called on cluster node connection event. This is a
* notification to the component that a new node has connected to the system.
*
* @param node
* is a hostname of a new cluster node connected to the system.
*/
@Override
public void nodeConnected(String node) {
log.log(Level.FINE, "Nodes connected: {0}", node);
JID jid = JID.jidInstanceNS(getName(), node, null);
strategy.nodeConnected(jid);
sendAdminNotification("Cluster node '" + node + "' connected (" + (new Date()) + ")",
"New cluster node connected: " + node, node);
if (strategy.needsSync()) {
requestSync(jid);
}
}
/**
* Method is called on cluster node disconnection event. This is a
* notification to the component that there was network connection lost to one
* of the cluster nodes.
*
* @param node
* is a hostname of a cluster node generating the event.
*/
@Override
public void nodeDisconnected(String node) {
log.log(Level.FINE, "Nodes disconnected: {0}", node);
JID jid = JID.jidInstanceNS(getName(), node, null);
strategy.nodeDisconnected(jid);
// Not sure what to do here, there might be still packets
// from the cluster node waiting....
// delTrusted(jid);
sendAdminNotification("Cluster node '" + node + "' disconnected (" + (new Date())
+ ")", "Cluster node disconnected: " + node, node);
}
/**
* A utility method used to prepare a Map of data with user session data
* before it can be sent over to another cluster node. This is supposed to
* contain all the user's session essential information which directly
* identify user's resource and network connection. This information allows to
* detect two different user's connection made for the same resource. This may
* happen if both connections are established to different nodes.
*
* @param conn
* is user's XMPPResourceConnection for which Map structure is
* prepare.
*
* @return a Map structure with all user's connection essential data.
*
* @throws NoConnectionIdException
* @throws NotAuthorizedException
*/
protected Map<String, String> prepareConnectionParams(XMPPResourceConnection conn)
throws NotAuthorizedException, NoConnectionIdException {
Map<String, String> params = new LinkedHashMap<String, String>();
params.put(USER_ID, conn.getBareJID().toString());
params.put(RESOURCE, conn.getResource());
params.put(CONNECTION_ID, conn.getConnectionId().toString());
params.put(XMPP_SESSION_ID, conn.getSessionId());
params.put(AUTH_TIME, "" + conn.getAuthTime());
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for conn: {0}, result: ",
new Object[] { conn, params });
}
return params;
}
// private int tIdx = 0;
// private int maxIdx = 100;
// private long[] commandTime = new long[maxIdx];
// private long[] clusterTime = new long[maxIdx];
// private long[] checkingTime = new long[maxIdx];
// private long[] smTime = new long[maxIdx];
/**
* This is a standard component method for processing packets. The method
* takes care of cases where the packet cannot be processed locally, in such a
* case it is forwarded to another node.
*
*
* @param packet
*/
@Override
public void processPacket(Packet packet) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Received packet: {0}", packet);
}
// long startTime = System.currentTimeMillis();
// int idx = tIdx;
// tIdx = (tIdx + 1) % maxIdx;
// long cmdTm = 0;
// long clTm = 0;
// long chTm = 0;
// long smTm = 0;
if (packet.isCommand() && processCommand(packet)) {
packet.processedBy("SessionManager");
// cmdTm = System.currentTimeMillis() - startTime;
} else {
XMPPResourceConnection conn = getXMPPResourceConnection(packet);
List<JID> toNodes =
strategy.getNodesForPacketForward(getComponentId(), null, packet);
if (toNodes != null && toNodes.size() > 0) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Forwarding packet {0} to nodes: {1}", new Object[] {
packet, toNodes });
}
Map<String, String> data = null;
if (conn != null) {
data = new LinkedHashMap<String, String>();
data.put(SESSION_FOUND_KEY, getComponentId().toString());
}
clusterController.sendToNodes(PACKET_FORWARD_CMD, data, packet.getElement(),
getComponentId(), null, toNodes.toArray(new JID[toNodes.size()]));
} else {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "No cluster nodes found for packet forward: {0}",
new Object[] { packet });
}
}
// clTm = System.currentTimeMillis() - startTime;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Ressource connection found: {0}", conn);
}
if (conn == null) {
if (isBrokenPacket(packet) || processAdminsOrDomains(packet)) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Ignoring/dropping packet: {0}", packet);
}
} else {
// Process is as packet to offline user only if there are no other
// nodes for the packet to be processed.
if (toNodes == null || toNodes.size() == 0) {
// Process packet for offline user
processPacket(packet, (XMPPResourceConnection) null);
}
}
} else {
processPacket(packet, conn);
// smTm = System.currentTimeMillis() - startTime;
}
}
// commandTime[idx] = cmdTm;
// clusterTime[idx] = clTm;
// checkingTime[idx] = chTm;
// smTime[idx] = smTm;
}
/**
* Method attempts to send the packet to the next cluster node. Returns true
* on successful attempt and false on failure. The true result does not mean
* that the packet has been delivered though. Only that it was sent. The send
* attempt may fail if there is no more cluster nodes to send the packet or if
* the clustering strategy logic decided that the packet does not have to be
* sent.
*
* @param packet
* to be sent to a next cluster node
* @param visitedNodes
* a list of nodes already visited by the packet.
* @return true if the packet was sent to next cluster node and false
* otherwise.
*/
protected boolean sendToNextNode(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Packet packet) {
boolean result = false;
List<JID> nextNodes =
strategy.getNodesForPacketForward(fromNode, visitedNodes, packet);
if (nextNodes != null && nextNodes.size() > 0) {
clusterController.sendToNodes(PACKET_FORWARD_CMD, data, packet.getElement(),
fromNode, visitedNodes, nextNodes.toArray(new JID[nextNodes.size()]));
result = true;
}
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for packet: {0}, visitedNodes: {1}, result: {2}",
new Object[] { packet, visitedNodes, result });
}
return result;
}
/**
* Concurrency control method. Returns preferable number of threads set for
* this component.
*
*
* @return preferable number of threads set for this component.
*/
@Override
public int processingInThreads() {
return Math.max(nodesNo, super.processingInThreads());
}
/**
* Concurrency control method. Returns preferable number of threads set for
* this component.
*
*
* @return preferable number of threads set for this component.
*/
@Override
public int processingOutThreads() {
return Math.max(nodesNo, super.processingOutThreads());
}
/**
* Set's the configures the cluster controller object for cluster
* communication and API.
*
* @param cl_controller
*/
@Override
public void setClusterController(ClusterControllerIfc cl_controller) {
clusterController = cl_controller;
clusterController.removeCommandListener(USER_CONNECTED_CMD, userConnected);
clusterController.removeCommandListener(USER_DISCONNECTED_CMD, userDisconnected);
clusterController.removeCommandListener(USER_PRESENCE_CMD, userPresence);
clusterController.removeCommandListener(PACKET_FORWARD_CMD, packetForward);
clusterController.removeCommandListener(REQUEST_SYNCONLINE_CMD, requestSyncOnline);
clusterController.removeCommandListener(RESPOND_SYNCONLINE_CMD, respondSyncOnline);
clusterController.setCommandListener(USER_CONNECTED_CMD, userConnected);
clusterController.setCommandListener(USER_DISCONNECTED_CMD, userDisconnected);
clusterController.setCommandListener(USER_PRESENCE_CMD, userPresence);
clusterController.setCommandListener(PACKET_FORWARD_CMD, packetForward);
clusterController.setCommandListener(REQUEST_SYNCONLINE_CMD, requestSyncOnline);
clusterController.setCommandListener(RESPOND_SYNCONLINE_CMD, respondSyncOnline);
}
/**
* Standard component's configuration method.
*
*
* @param props
*/
@Override
public void setProperties(Map<String, Object> props) {
super.setProperties(props);
if (props.get(STRATEGY_CLASS_PROP_KEY) != null) {
String strategy_class = (String) props.get(STRATEGY_CLASS_PROP_KEY);
try {
ClusteringStrategyIfc strategy_tmp =
(ClusteringStrategyIfc) Class.forName(strategy_class).newInstance();
strategy_tmp.setProperties(props);
// strategy_tmp.init(getName());
strategy = strategy_tmp;
strategy.setSessionManagerHandler(this);
log.log(Level.CONFIG, "Loaded SM strategy: " + strategy_class);
// strategy.nodeConnected(getComponentId());
addTrusted(getComponentId());
} catch (Exception e) {
log.log(Level.SEVERE, "Can not clustering strategy instance for class: "
+ strategy_class, e);
}
}
try {
if (props.get(MY_DOMAIN_NAME_PROP_KEY) != null) {
my_hostname = JID.jidInstance((String) props.get(MY_DOMAIN_NAME_PROP_KEY));
my_address =
JID.jidInstance(getName(), (String) props.get(MY_DOMAIN_NAME_PROP_KEY), null);
}
} catch (TigaseStringprepException ex) {
log.log(Level.WARNING,
"Creating component source address failed stringprep processing: {0}@{1}",
new Object[] { getName(), my_hostname });
}
}
/**
* The method intercept user's disconnect event. On user disconnect the method
* takes a list of cluster nodes from the strategy and sends a notification to
* all those nodes about the event.
*
* @see tigase.server.xmppsession.SessionManager#closeSession(tigase.xmpp.
* XMPPResourceConnection, boolean)
*/
@Override
protected void closeSession(XMPPResourceConnection conn, boolean closeOnly) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "Called for conn: {0}, closeOnly: {1}", new Object[] { conn,
closeOnly });
}
// Exception here should not normally happen, but if it does, then
// consequences might be severe, let's catch it then
try {
if (conn.isAuthorized() && conn.isResourceSet()) {
Map<String, String> params = prepareConnectionParams(conn);
List<JID> cl_nodes = strategy.getNodesForUserDisconnect(conn.getJID());
++clusterSyncOutTraffic;
clusterController.sendToNodes(USER_DISCONNECTED_CMD, params, getComponentId(),
cl_nodes.toArray(new JID[cl_nodes.size()]));
}
} catch (Exception ex) {
log.log(Level.WARNING, "This should not happen, check it out!, ", ex);
}
// Exception here should not normally happen, but if it does, then
// consequences might be severe, let's catch it then
try {
super.closeSession(conn, closeOnly);
} catch (Exception ex) {
log.log(Level.WARNING, "This should not happen, check it out!, ", ex);
}
}
/**
* Method takes the data received from other cluster node and creates a
* ConnectionRecord with all essential connection information. This might be
* used later to identify user's XMPPResourceConnection or use the clustering
* strategy API.
*
* @param node
* @param data
* @return
*/
protected ConnectionRecord getConnectionRecord(JID node, Map<String, String> data) {
BareJID userId = BareJID.bareJIDInstanceNS(data.get(USER_ID));
String resource = data.get(RESOURCE);
JID jid = JID.jidInstanceNS(userId, resource);
String sessionId = data.get(XMPP_SESSION_ID);
JID connectionId = JID.jidInstanceNS(data.get(CONNECTION_ID));
ConnectionRecord rec = new ConnectionRecord(node, jid, sessionId, connectionId);
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "ConnectionRecord created: {0}", new Object[] { rec });
}
return rec;
}
/**
* Send synchronization request to a given cluster node. In a response the
* remote node should return a list of JIDs for online users on this node.
*
* @param node
* is a JID of the target cluster node.
*/
protected void requestSync(JID node) {
++clusterSyncOutTraffic;
clusterController.sendToNodes(REQUEST_SYNCONLINE_CMD, getComponentId(), node);
}
private void sendAdminNotification(String msg, String subject, String node) {
String message = msg;
if (node != null) {
message = msg + "\n";
}
int cnt = 0;
message += node + " connected to " + getDefHostName();
Packet p_msg =
Message.getMessage(my_address, my_hostname, StanzaType.normal, message, subject,
"xyz", newPacketId(null));
sendToAdmins(p_msg);
}
private class RespondSyncOnlineCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> elements) throws ClusterCommandException {
++clusterSyncInTraffic;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, elements });
}
Queue<Packet> results = new ArrayDeque<Packet>();
ArrayList<ConnectionRecord> usrConns =
new ArrayList<ConnectionRecord>(elements.size());
for (Element elem : elements) {
usrConns.add(new ConnectionRecord(elem));
}
strategy.usersConnected(results,
usrConns.toArray(new ConnectionRecord[usrConns.size()]));
}
}
private class RequestSyncOnlineCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> packets) throws ClusterCommandException {
++clusterSyncInTraffic;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
// Send back all online users on this node
LinkedList<Element> usrConns = new LinkedList<Element>();
for (XMPPResourceConnection conn : connectionsByFrom.values()) {
try {
if (conn.isResourceSet()) {
ConnectionRecord cr =
new ConnectionRecord(getComponentId(), conn.getJID(),
conn.getSessionId(), conn.getConnectionId());
cr.setLastPresence(conn.getPresence());
usrConns.add(cr.toElement());
if (usrConns.size() > SYNC_MAX_BATCH_SIZE) {
clusterController.sendToNodes(RESPOND_SYNCONLINE_CMD, usrConns,
getComponentId(), null, fromNode);
usrConns = new LinkedList<Element>();
}
}
} catch (NotAuthorizedException ex) {
// Ignore, only authenticated connections are synchronized
} catch (NoConnectionIdException ex) {
// Ignore, only connections with valid connection ID are synchronized
}
}
if (usrConns.size() > 0) {
clusterController.sendToNodes(RESPOND_SYNCONLINE_CMD, usrConns, getComponentId(),
null, fromNode);
}
}
}
private class UserPresenceCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> packets) throws ClusterCommandException {
++clusterSyncInTraffic;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
ConnectionRecord rec = getConnectionRecord(fromNode, data);
XMPPSession session = getSession(rec.getUserJid().getBareJID());
Element elem = packets.poll();
// Notify strategy about presence update
strategy.presenceUpdate(elem, rec);
// Update all user's resources with the new presence
if (session != null) {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST, "User's {0} XMPPSession found: {1}", new Object[] {
rec.getUserJid().getBareJID(), session });
}
for (XMPPResourceConnection conn : session.getActiveResources()) {
Element conn_presence = conn.getPresence();
if (conn.isAuthorized() && conn.isResourceSet() && conn_presence != null) {
try {
// Send user's presence from remote connection to local connection
Packet presence = Packet.packetInstance(elem);
presence.setPacketTo(conn.getConnectionId());
fastAddOutPacket(presence);
// Send user's presence from local connection to remote connection
// but only if this was an initial presence
if (data != null
&& PRESENCE_TYPE_INITIAL.equals(data.get(PRESENCE_TYPE_KEY))) {
presence = Packet.packetInstance(conn_presence);
presence.setPacketTo(rec.getConnectionId());
fastAddOutPacket(presence);
}
} catch (Exception ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
}
}
} else {
if (log.isLoggable(Level.FINEST)) {
log.log(
Level.FINEST,
"No user session for presence update: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
}
if (log.isLoggable(Level.FINEST)) {
log.finest("User presence jid: " + rec.getUserJid() + ", fromNode: " + fromNode);
}
}
}
private class UserConnectedCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> packets) throws ClusterCommandException {
++clusterSyncInTraffic;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
Queue<Packet> results = new ArrayDeque<Packet>(10);
ConnectionRecord rec = getConnectionRecord(fromNode, data);
strategy.usersConnected(results, rec);
addOutPackets(results);
// There is one more thing....
// If the new connection is for the same resource we have here then the
// old connection must be destroyed.
XMPPSession session = getSession(rec.getUserJid().getBareJID());
if (session != null) {
XMPPResourceConnection conn =
session.getResourceForResource(rec.getUserJid().getResource());
if (conn != null) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Duplicate resource connection, logingout the older connection: "
+ rec);
}
try {
Packet cmd =
Command.CLOSE.getPacket(getComponentId(), conn.getConnectionId(),
StanzaType.set, conn.nextStanzaId());
Element err_el = new Element("conflict");
err_el.setXMLNS("urn:ietf:params:xml:ns:xmpp-streams");
cmd.getElement().getChild("command").addChild(err_el);
fastAddOutPacket(cmd);
} catch (Exception ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
}
}
if (log.isLoggable(Level.FINEST)) {
log.finest("User connected jid: " + rec.getUserJid() + ", fromNode: " + fromNode);
}
}
}
private class UserDisconnectedCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> packets) throws ClusterCommandException {
++clusterSyncInTraffic;
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
Queue<Packet> results = new ArrayDeque<Packet>(10);
ConnectionRecord rec = getConnectionRecord(fromNode, data);
strategy.userDisconnected(results, rec);
addOutPackets(results);
}
}
private class PacketForwardCommand implements CommandListener {
/*
* (non-Javadoc)
*
* @see tigase.cluster.api.CommandListener#executeCommand(java.util.Map)
*/
@Override
public void executeCommand(JID fromNode, Set<JID> visitedNodes,
Map<String, String> data, Queue<Element> packets) throws ClusterCommandException {
if (log.isLoggable(Level.FINEST)) {
log.log(Level.FINEST,
"Called fromNode: {0}, visitedNodes: {1}, data: {2}, packets: {3}",
new Object[] { fromNode, visitedNodes, data, packets });
}
if ((packets != null) && (packets.size() > 0)) {
for (Element elem : packets) {
try {
Packet el_packet = Packet.packetInstance(elem);
XMPPResourceConnection conn = getXMPPResourceConnection(el_packet);
Map<String, String> locdata = null;
if (conn != null) {
locdata = new LinkedHashMap<String, String>();
if (data != null) {
locdata.putAll(data);
}
data.put(SESSION_FOUND_KEY, getComponentId().toString());
}
// The commented if below causes the packet to stop being forwarded
// if it reached a host on which there is a user session to handle
// it.
// This is incorrect though, as there might be multiple users'
// connections
// to different nodes and each node should receive the packet.
// if (conn != null || !sendToNextNode(fromNode, visitedNodes, data,
// Packet.packetInstance(elem))) {
// Instead, always send the packet to next node:
boolean isSent =
sendToNextNode(fromNode, visitedNodes, data, Packet.packetInstance(elem));
// If there is a user session for the packet, process it
if (conn != null) {
// Hold on! If this is the first node (fromNode) it means the
// packet was already processed here....
if (!getComponentId().equals(fromNode)) {
processPacket(el_packet, conn);
} else {
// Ignore the packet, it has been processed already
}
} else {
// No user session, but if this is the first node the packet has
// returned, so maybe this is a packet for offline storage?
if (getComponentId().equals(fromNode)) {
// However it could have been processed on another node already
if (data == null || data.get(SESSION_FOUND_KEY) == null) {
processPacket(el_packet, conn);
}
}
}
} catch (TigaseStringprepException ex) {
log.warning("Addressing problem, stringprep failed for packet: " + elem);
}
}
} else {
log.finest("Empty packets list in the forward command");
}
}
}
}