/*
* 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, 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.api;
import tigase.server.Packet;
import tigase.xml.Element;
import tigase.xmpp.JID;
import tigase.xmpp.StanzaType;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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;
/**
* Class ClusterElement is a utility class for handling tigase cluster specific
* packets. The cluster packet has the following form:
*
* <pre>
* <cluster xmlns="tigase:cluster" from="source" to="dest" type="set">
* <data>
* <message xmlns="jabber:client" from="source-u" to="dest-x" type="chat">
* <body>Hello world!</body>
* </message>
* </data>
* <control>
* <first-node>node1 JID address</first-node>
* <visited-nodes>
* <node-id>node1 JID address</node-id>
* <node-id>node2 JID address</node-id>
* </visited-nodes>
* <method-call name="method name">
* <par name="param1 name">value</par>
* <par name="param2 name">value</par>
* <results>
* <val name="val1 name">value</var>
* <val name="val2 name">value</var>
* </results>
* </method-call>
* </control>
* </cluster>
* </pre>
*
* If none of nodes could process the packet it goes back to the first node as
* this node is the most likely to process the packet correctly.
*
*
* Created: Fri May 2 09:40:40 2008
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public class ClusterElement {
/** Field description */
public static final String XMLNS = "tigase:cluster";
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger("tigase.cluster.ClusterElement");
/** Field description */
public static final String CLUSTER_CONTROL_EL_NAME = "control";
/** Field description */
public static final String CLUSTER_DATA_EL_NAME = "data";
/** Field description */
public static final String CLUSTER_EL_NAME = "cluster";
/** Field description */
public static final String CLUSTER_METHOD_EL_NAME = "method-call";
/** Field description */
public static final String CLUSTER_METHOD_PAR_EL_NAME = "par";
/** Field description */
public static final String CLUSTER_METHOD_RESULTS_EL_NAME = "results";
/** Field description */
public static final String CLUSTER_METHOD_RESULTS_VAL_EL_NAME = "val";
/** Field description */
public static final String CLUSTER_NAME_ATTR = "name";
/** Field description */
public static final String FIRST_NODE_EL_NAME = "first-node";
/** Field description */
public static final String NODE_ID_EL_NAME = "node-id";
/** Field description */
public static final String VISITED_NODES_EL_NAME = "visited-nodes";
/** Field description */
public static final String CLUSTER_CONTROL_PATH = "/" + CLUSTER_EL_NAME + "/"
+ CLUSTER_CONTROL_EL_NAME;
/** Field description */
public static final String CLUSTER_METHOD_PATH = "/" + CLUSTER_CONTROL_PATH + "/"
+ CLUSTER_METHOD_EL_NAME;
/** Field description */
public static final String VISITED_NODES_PATH = CLUSTER_CONTROL_PATH + "/"
+ VISITED_NODES_EL_NAME;
// public static final String PACKET_FROM_ATTR_NAME = "packet-from";
/** Field description */
public static final String FIRST_NODE_PATH = CLUSTER_CONTROL_PATH + "/"
+ FIRST_NODE_EL_NAME;
/** Field description */
public static final String CLUSTER_METHOD_RESULTS_PATH = CLUSTER_METHOD_PATH + "/"
+ CLUSTER_METHOD_RESULTS_EL_NAME;
/** Field description */
public static final String CLUSTER_DATA_PATH = "/" + CLUSTER_EL_NAME + "/"
+ CLUSTER_DATA_EL_NAME;
private Element elem = null;
private JID first_node = null;
private String method_name = null;
private Map<String, String> method_params = null;
private Map<String, String> method_results = null;
private Queue<Element> packets = null;
private Set<JID> visited_nodes = null;
/**
* Creates a new <code>ClusterElement</code> instance.
*
*
* @param elem
*/
public ClusterElement(Element elem) {
this.elem = elem;
if (log.isLoggable(Level.FINEST)) {
log.finest("Parsing cluster element: " + elem.toString());
}
List<Element> children = elem.getChildren(CLUSTER_DATA_PATH);
if (children != null && children.size() > 0) {
packets = new ArrayDeque<Element>(children);
}
String fNode = elem.getCData(FIRST_NODE_PATH);
if (fNode != null) {
first_node = JID.jidInstanceNS(fNode);
}
if (log.isLoggable(Level.FINEST)) {
log.finest("First node found: " + first_node);
}
visited_nodes = new LinkedHashSet<JID>();
List<Element> nodes = elem.getChildren(VISITED_NODES_PATH);
if (nodes != null) {
int cnt = 0;
for (Element node : nodes) {
visited_nodes.add(JID.jidInstanceNS(node.getCData()));
++cnt;
}
if (log.isLoggable(Level.FINEST)) {
log.finest("Found and added visited nodes: " + cnt);
}
} else {
if (log.isLoggable(Level.FINEST)) {
log.finest("No visited nodes found");
}
}
Element method_call = elem.findChild(CLUSTER_METHOD_PATH);
if (method_call != null) {
parseMethodCall(method_call);
}
}
/**
* Constructs ...
*
*
* @param from
* @param to
* @param type
* @param packet
*/
public ClusterElement(JID from, JID to, StanzaType type, Packet packet) {
if (packet != null) {
packets = new ArrayDeque<Element>();
visited_nodes = new LinkedHashSet<JID>();
elem = createClusterElement(from, to, type, packet.getFrom().toString());
if (packet.getElement().getXMLNS() == null) {
packet.getElement().setXMLNS("jabber:client");
}
addDataPacket(packet);
}
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
*
* @return
*/
public static Element clusterElement(JID from, JID to, StanzaType type) {
Element cluster_el =
new Element(CLUSTER_EL_NAME, new String[] { "from", "to", "type" }, new String[] {
from.toString(), to.toString(), type.toString() });
cluster_el.setXMLNS(XMLNS);
cluster_el.addChild(new Element(CLUSTER_CONTROL_EL_NAME, new Element[] { new Element(
VISITED_NODES_EL_NAME) }, null, null));
return cluster_el;
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param packet_from
*
* @return
*/
public static Element createClusterElement(JID from, JID to, StanzaType type,
String packet_from) {
Element cluster_el = clusterElement(from, to, type);
cluster_el.addChild(new Element(CLUSTER_DATA_EL_NAME));
// new String[] {PACKET_FROM_ATTR_NAME}, new String[] {packet_from}));
return cluster_el;
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param method_name
* @param params
*
* @return
*/
public static ClusterElement createClusterMethodCall(JID from, JID to, StanzaType type,
String method_name, Map<String, String> params) {
Element cluster_el = clusterElement(from, to, type);
Element method_call =
new Element(CLUSTER_METHOD_EL_NAME, new String[] { CLUSTER_NAME_ATTR },
new String[] { method_name });
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
method_call.addChild(new Element(CLUSTER_METHOD_PAR_EL_NAME, entry.getValue(),
new String[] { CLUSTER_NAME_ATTR }, new String[] { entry.getKey() }));
}
}
cluster_el.findChild(CLUSTER_CONTROL_PATH).addChild(method_call);
ClusterElement result_cl = new ClusterElement(cluster_el);
result_cl.addVisitedNode(from);
return result_cl;
}
/**
* Method description
*
*
* @param clel
* @param cluster_nodes
* @param comp_id
*
* @return
*/
public static ClusterElement createForNextNode(ClusterElement clel,
List<JID> cluster_nodes, JID comp_id) {
if (log.isLoggable(Level.FINEST)) {
log.finest("Calculating a next node from nodes: "
+ ((cluster_nodes != null) ? cluster_nodes.toString() : "null"));
}
if ((cluster_nodes != null) && (cluster_nodes.size() > 0)) {
JID next_node = null;
for (JID cluster_node : cluster_nodes) {
if (!clel.isVisitedNode(cluster_node) && !cluster_node.equals(comp_id)) {
next_node = cluster_node;
if (log.isLoggable(Level.FINEST)) {
log.finest("Found next cluster node: " + next_node);
}
break;
}
}
if (next_node != null) {
ClusterElement result = clel.nextClusterNode(next_node);
result.addVisitedNode(comp_id);
return result;
}
}
return null;
}
/**
* Method description
*
*
* @param packet
*/
public void addDataPacket(Packet packet) {
addDataPacket(packet.getElement());
}
/**
* Method description
*
*
* @param packet
*/
public void addDataPacket(Element packet) {
if (packets == null) {
packets = new ArrayDeque<Element>();
}
packets.offer(packet);
if (elem.findChild(CLUSTER_DATA_PATH) == null) {
elem.addChild(new Element(CLUSTER_DATA_EL_NAME));
}
elem.findChild(CLUSTER_DATA_PATH).addChild(packet);
}
public void addDataPackets(Queue<Element> packets) {
if (packets != null) {
for (Element elem : packets) {
addDataPacket(elem);
}
}
}
/**
* Method description
*
*
* @param key
* @param val
*/
public void addMethodResult(String key, String val) {
Element res = elem.findChild(CLUSTER_METHOD_RESULTS_PATH);
if (res == null) {
res = new Element(CLUSTER_METHOD_RESULTS_EL_NAME);
elem.findChild(CLUSTER_METHOD_PATH).addChild(res);
}
res.addChild(new Element(CLUSTER_METHOD_RESULTS_VAL_EL_NAME, val,
new String[] { CLUSTER_NAME_ATTR }, new String[] { key }));
if (method_results == null) {
method_results = new LinkedHashMap<String, String>();
}
method_results.put(key, val);
}
/**
* Method description
*
*
* @param node_id
*/
public void addVisitedNode(JID node_id) {
if (visited_nodes.size() == 0) {
first_node = node_id;
elem.findChild(CLUSTER_CONTROL_PATH).addChild(
new Element(FIRST_NODE_EL_NAME, node_id.toString()));
}
if (visited_nodes.add(node_id)) {
elem.findChild(VISITED_NODES_PATH).addChild(
new Element(NODE_ID_EL_NAME, node_id.toString()));
}
}
public void addVisitedNodes(Set<JID> nodes) {
if (nodes != null) {
for (JID node : nodes) {
addVisitedNode(node);
}
}
}
/**
* Method description
*
*
* @param from
* @param type
* @param results
*
* @return
*/
public ClusterElement createMethodResponse(JID from, StanzaType type,
Map<String, String> results) {
return createMethodResponse(from, null, type, results);
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param results
*
* @return
*/
public ClusterElement createMethodResponse(JID from, JID to, StanzaType type,
Map<String, String> results) {
Element result_el = elem.clone();
result_el.setAttribute("from", from.toString());
result_el.setAttribute("to", ((to != null) ? to.toString() : first_node.toString()));
result_el.setAttribute("type", type.name());
Element res = new Element(CLUSTER_METHOD_RESULTS_EL_NAME);
result_el.findChild(CLUSTER_METHOD_PATH).addChild(res);
ClusterElement result_cl = new ClusterElement(result_el);
if (results != null) {
for (Map.Entry<String, String> entry : results.entrySet()) {
result_cl.addMethodResult(entry.getKey(), entry.getValue());
}
}
return result_cl;
}
/**
* Method description
*
*
* @return
*/
public Map<String, String> getAllMethodParams() {
return method_params;
}
/**
* Method description
*
*
* @return
*/
public Map<String, String> getAllMethodResults() {
return method_results;
}
/**
* Method description
*
*
* @return
*/
public Element getClusterElement(String id) {
elem.setAttribute("id", id);
return elem;
}
/**
* Method description
*
*
* @return
*/
public Queue<Element> getDataPackets() {
return packets;
}
/**
* Method description
*
*
* @return
*/
public JID getFirstNode() {
return first_node;
}
/**
* Method description
*
*
* @return
*/
public String getMethodName() {
return method_name;
}
/**
* Method description
*
*
* @param par_name
*
* @return
*/
public String getMethodParam(String par_name) {
return (method_params == null) ? null : method_params.get(par_name);
}
/**
* Method description
*
*
* @param par_name
* @param def
*
* @return
*/
public long getMethodParam(String par_name, long def) {
String val_str = getMethodParam(par_name);
if (val_str == null) {
return def;
} else {
try {
return Long.parseLong(val_str);
} catch (NumberFormatException e) {
return def;
}
}
}
/**
* Method description
*
*
* @param val_name
*
* @return
*/
public String getMethodResultVal(String val_name) {
return (method_results == null) ? null : method_results.get(val_name);
}
/**
* Method description
*
*
* @param val_name
* @param def
*
* @return
*/
public long getMethodResultVal(String val_name, long def) {
String val_str = getMethodResultVal(val_name);
if (val_str == null) {
return def;
} else {
try {
return Long.parseLong(val_str);
} catch (NumberFormatException e) {
return def;
}
}
}
/**
* Method description
*
*
* @return
*/
public Set<JID> getVisitedNodes() {
return visited_nodes;
}
/**
* Method description
*
*
* @param node_id
*
* @return
*/
public boolean isVisitedNode(JID node_id) {
return visited_nodes.contains(node_id);
}
/**
* Method description
*
*
* @param node_id
*
* @return
*/
public ClusterElement nextClusterNode(JID node_id) {
Element next_el = elem.clone();
String from = elem.getAttribute("to");
next_el.setAttribute("from", from);
next_el.setAttribute("to", node_id.toString());
// next_el.setAttribute("type", StanzaType.set.toString());
ClusterElement next_cl = new ClusterElement(next_el);
return next_cl;
}
protected void parseMethodCall(Element method_call) {
method_name = method_call.getAttribute(CLUSTER_NAME_ATTR);
if (method_name != null) {
method_name = method_name.intern();
}
method_params = new LinkedHashMap<String, String>();
List<Element> children = method_call.getChildren();
if (children != null) {
for (Element child : children) {
if (child.getName() == CLUSTER_METHOD_PAR_EL_NAME) {
String par_name = child.getAttribute(CLUSTER_NAME_ATTR);
method_params.put(par_name, child.getCData());
}
if (child.getName() == CLUSTER_METHOD_RESULTS_EL_NAME) {
if (method_results == null) {
method_results = new LinkedHashMap<String, String>();
}
List<Element> res_children = child.getChildren();
if (res_children != null) {
for (Element res_child : res_children) {
if (res_child.getName() == CLUSTER_METHOD_RESULTS_VAL_EL_NAME) {
String val_name = res_child.getAttribute(CLUSTER_NAME_ATTR);
method_results.put(val_name, res_child.getCData());
}
}
}
}
}
}
}
}