/*
* 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.server;
//~--- non-JDK imports --------------------------------------------------------
import tigase.xml.Element;
import tigase.xml.XMLUtils;
import tigase.xmpp.JID;
import tigase.xmpp.StanzaType;
//~--- JDK imports ------------------------------------------------------------
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
//~--- enums ------------------------------------------------------------------
/**
* Helper enum to make it easier to operate on packets with ad-hoc commands. It
* allows to create a packet with command, add and retrieve command data field
* values, set actions and so on.
*
* It contains predefined set of commands used internally by the Tigase server
* and also 'OTHER' command which refers all other not predefined commands.
*
* Most of the implementation details, constants and parameters is based on the
* <a href="http://xmpp.org/extensions/xep-0050.html">XEP-0050</a> for ad-hoc
* commands protocol. Please refer to the XEP for more details.
*
* Created: Thu Feb 9 20:52:02 2006
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev$
*/
public enum Command {
/**
* Command sent from a connection manager to the session manager when a new
* stream from the client has been opened.
*/
STREAM_OPENED(Priority.SYSTEM),
/**
* Command sent from a connection manager to the session manager when a
* connection or stream has been closed.
*/
STREAM_CLOSED(Priority.SYSTEM), STREAM_CLOSED_UPDATE(Priority.SYSTEM),
/**
* Sends a command from SM to the connection holder to confirm whether the
* connection is still active. Expects result for ok, error or timeout if the
* connection is no longer active.
*/
CHECK_USER_CONNECTION(Priority.SYSTEM),
/**
* Command sent from the session manager to a connection manager to start TLS
* handshaking over the client connection.
*/
STARTTLS(Priority.NORMAL),
/**
* Command sent from the session manager to a connection manager to start zlib
* compression on the connection stream.
*/
STARTZLIB(Priority.NORMAL),
/**
* Command sent between a connection manager and the session manager to
* retrieve stream features.
*/
GETFEATURES(Priority.HIGH),
/**
* This is depreciated command sent between components in the Tigase server
* for service discovery handling.
*/
GETDISCO(Priority.HIGH),
/**
* Command sent from the session manager to a client manager to close the
* client connection.
*/
CLOSE(Priority.SYSTEM),
/**
* Command used by the StatisticsCollector to provide server statistics
* through ad-hoc command.
*/
GETSTATS(Priority.HIGH),
/**
* Command sent to the session manager from an external entity to activate a
* user session with the connection end-point at the given address.
*/
USER_STATUS(Priority.NORMAL),
/**
* Command used to set a broadcast message to all online users.
*/
BROADCAST_TO_ONLINE(Priority.NORMAL),
/**
* Command used to set a broadcast message to all registered local users.
*/
BROADCAST_TO_ALL(Priority.NORMAL),
/**
* Command used to redirect packets from a connection manager to other than
* default session manager. (Mostly used in the clustering.)
*/
REDIRECT(Priority.SYSTEM),
/**
* Command sent to the VHostManager to reload virtual hosts from the database.
*/
VHOSTS_RELOAD(Priority.NORMAL),
/**
* Command sent to the VHostManager to add or update existing virtual host.
*/
VHOSTS_UPDATE(Priority.NORMAL),
/**
* Command sent to the VHostManager to remove existing virtual host.
*/
VHOSTS_REMOVE(Priority.NORMAL),
/**
* Identifies all other, not predefined commands.
*/
OTHER(Priority.NORMAL);
/**
* Variable <code>log</code> is a class logger.
*/
private static final Logger log = Logger.getLogger("tigase.server.Command");
/** Field description */
public static final String XMLNS = "http://jabber.org/protocol/commands";
/** Field description */
public static final String COMMAND_EL = "command";
// ~--- constant enums -------------------------------------------------------
/**
* Ad-hoc command actions ad defined in the XEP-0050.
*/
public enum Action {
/**
* The command should be executed or continue to be executed. This is the
* default value.
*/
execute,
/**
* The command should be canceled.
*/
cancel,
/**
* The command should be digress to the previous stage of execution.
*/
prev,
/**
* The command should progress to the next stage of execution.
*/
next,
/**
* The command should be completed (if possible).
*/
complete,
/**
* Other, not recognized command action.
*/
other;
}
/**
* Data form-types as defined in the XEP-0050.
*/
public enum DataType {
/**
* This is a form with ad-hoc command result data.
*/
result,
/**
* This is a form querying for more data from the user.
*/
form,
/**
* Form filled with data sent as a response to 'form' request.
*/
submit;
}
/**
* Ad-hoc command statuses as defined in the XEP-0050.
*/
public enum Status {
/**
* The command is being executed.
*/
executing,
/**
* The command has completed. The command session has ended.
*/
completed,
/**
* The command has been canceled. The command session has ended.
*/
canceled,
/**
* Other, not recognized command status.
*/
other;
}
// ~--- fields ---------------------------------------------------------------
private Priority priority = Priority.NORMAL;
// ~--- constructors ---------------------------------------------------------
private Command(Priority priority) {
this.priority = priority;
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param packet
* @param action
*/
public static void addAction(final Packet packet, final Action action) {
addActionEl(packet.getElement(), action);
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
*/
public static void addCheckBoxField(Packet packet, String f_name, boolean f_value) {
addFieldValue(packet, f_name, Boolean.toString(f_value), "boolean");
}
/**
* A simple method for adding a multi-line (text-multi) data field to the
* command data form. Only field name (variable name) and field default value
* can be set.
*
* @param packet
* is a <code>Packet</code> instance of the ad-hoc command request to
* be modified.
* @param f_name
* is a <code>String</code> instance with the field name. In ad-hoc
* command terms this is a variable name. This field name (variable
* name) will be also displayed as the field label.
* @param f_value
* is a list with lines of text to be displayed as a multi-line field
* content.
*/
public static void addFieldMultiValue(final Packet packet, final String f_name,
final List<String> f_value) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
if ( f_value != null ){
Element field =
new Element("field", new String[] { "var", "type" }, new String[] {
XMLUtils.escape(f_name), "text-multi" });
for (String val : f_value) {
if (val != null) {
Element value = new Element("value", XMLUtils.escape(val));
field.addChild(value);
}
}
x.addChild(field);
}
}
public static void addFieldMultiValue(final Packet packet, final String f_name,
final Throwable ex) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
List<String> f_value = null;
if (ex != null) {
f_value = new ArrayList<String>(100);
f_value.add(ex.getLocalizedMessage());
for (StackTraceElement ste : ex.getStackTrace()) {
f_value.add(" " + ste.toString());
}
}
if ( f_value != null ){
Element field =
new Element("field", new String[] { "var", "type" }, new String[] {
XMLUtils.escape(f_name), "text-multi" });
for (String val : f_value) {
if (val != null) {
Element value = new Element("value", XMLUtils.escape(val));
field.addChild(value);
}
}
x.addChild(field);
}
}
/**
* Simple method for adding a new field to the command data form. Only field
* name (variable name) and field default value can be set.
*
* @param packet
* is a <code>Packet</code> instance of the ad-hoc command request to
* be modified.
* @param f_name
* is a <code>String</code> instance with the field name. In ad-hoc
* command terms this is a variable name. This field name (variable
* name) will be also displayed as the field label.
* @param f_value
* is a <code>String</code> instance with the field default value.
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String f_value) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element("field", new Element[] { new Element("value",
XMLUtils.escape(f_value)) }, new String[] { "var" },
new String[] { XMLUtils.escape(f_name) });
x.addChild(field);
}
/**
* This method allows to add a new multi-option-select-one data field to the
* command data form. This is much more complex implementation allowing to set
* a field label and labels for all provided field options. It allows the
* end-user to select a single option from a given list.
*
* @param packet
* is a <code>Packet</code> instance of the ad-hoc command request to
* be modified.
* @param f_name
* is a <code>String</code> instance with the field name. In ad-hoc
* command terms this is a variable name.
* @param f_value
* is a <code>String</code> instance with the field default value. It
* must match one of the options vaulues provided as a list in
* 'options' parameter.
* @param label
* is a <code>String</code> instance with the field label. This time
* a label set here is displayed to the user instead of the field
* name (variable name). This is useful if the variable name is not
* suitable or clear enough to the end-user.
* @param labels
* is an array with options labels which are displayed to the
* end-user upon presenting the selection options.
* @param options
* is an array with options values to be selected by the end-user.
* Normally these values are not displayed to the end-user. Only
* options labels are.
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String f_value, final String label, final String[] labels,
final String[] options) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element(
"field",
new Element[] { new Element("value", XMLUtils.escape(f_value)) },
new String[] { "var", "type", "label" },
new String[] { XMLUtils.escape(f_name), "list-single", XMLUtils.escape(label) });
for (int i = 0; i < labels.length; i++) {
field.addChild(new Element("option", new Element[] { new Element("value", XMLUtils
.escape(options[i])) }, new String[] { "label" }, new String[] { XMLUtils
.escape(labels[i]) }));
}
x.addChild(field);
}
/**
* This method allows to add a new multi-option-select-many data field to the
* command data form. This is much more complex implementation allowing to set
* a field label and labels for all provided field options. It allows the
* end-user to select many options from the given list.
*
* @param packet
* is a <code>Packet</code> instance of the ad-hoc command request to
* be modified.
* @param f_name
* is a <code>String</code> instance with the field name. In ad-hoc
* command terms this is a variable name.
* @param f_values
* is an array of default values which are presented to the end user
* as preselected options. They must match options vaulues provided
* as a list in 'options' parameter.
* @param label
* is a <code>String</code> instance with the field label. This time
* a label set here is displayed to the user instead of the field
* name (variable name). This is useful if the variable name is not
* suitable or clear enough to the end-user.
* @param labels
* is an array with options labels which are displayed to the
* end-user upon presenting the selection options.
* @param options
* is an array with options values to be selected by the end-user.
* Normally these values are not displayed to the end-user. Only
* options labels are.
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String[] f_values, final String label, final String[] labels,
final String[] options) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element("field", new String[] { "var", "type", "label" }, new String[] {
XMLUtils.escape(f_name), "list-multi", XMLUtils.escape(label) });
for (int i = 0; i < labels.length; i++) {
field.addChild(new Element("option", new Element[] { new Element("value", XMLUtils
.escape(options[i])) }, new String[] { "label" }, new String[] { XMLUtils
.escape(labels[i]) }));
}
for (int i = 0; i < f_values.length; i++) {
field.addChild(new Element("value", XMLUtils.escape(f_values[i])));
}
x.addChild(field);
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
* @param label
* @param labels
* @param options
* @param type
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String f_value, final String label, final String[] labels,
final String[] options, final String type) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element("field", new Element[] { new Element("value",
XMLUtils.escape(f_value)) }, new String[] { "var", "type", "label" },
new String[] { XMLUtils.escape(f_name), type, XMLUtils.escape(label) });
for (int i = 0; i < labels.length; i++) {
field.addChild(new Element("option", new Element[] { new Element("value", XMLUtils
.escape(options[i])) }, new String[] { "label" }, new String[] { XMLUtils
.escape(labels[i]) }));
}
x.addChild(field);
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
* @param type
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String f_value, final String type) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element("field", new Element[] { new Element("value",
XMLUtils.escape(f_value)) }, new String[] { "var", "type" }, new String[] {
XMLUtils.escape(f_name), type });
x.addChild(field);
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
* @param type
* @param label
*/
public static void addFieldValue(final Packet packet, final String f_name,
final String f_value, final String type, final String label) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
Element field =
new Element("field", new Element[] { new Element("value",
XMLUtils.escape(f_value)) }, new String[] { "var", "type", "label" },
new String[] { XMLUtils.escape(f_name), type, XMLUtils.escape(label) });
x.addChild(field);
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
*/
public static void addHiddenField(Packet packet, String f_name, String f_value) {
addFieldValue(packet, f_name, f_value, "hidden");
}
/**
* Method description
*
*
* @param packet
* @param instructions
*/
public static void addInstructions(final Packet packet, final String instructions) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
x.addChild(new Element("instructions", instructions));
}
/**
* Method description
*
*
* @param packet
* @param note
*/
public static void addNote(final Packet packet, final String note) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element notes = command.getChild("note");
if (notes == null) {
notes = new Element("note", new String[] { "type" }, new String[] { "info" });
command.addChild(notes);
}
notes.setCData(XMLUtils.escape(note));
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param f_value
*/
public static void addTextField(Packet packet, String f_name, String f_value) {
addFieldValue(packet, f_name, f_value, "fixed");
}
/**
* Method description
*
*
* @param packet
* @param title
*/
public static void addTitle(final Packet packet, final String title) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
x = addDataForm(command, DataType.submit);
}
x.addChild(new Element("title", title));
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param id
* @param node
* @param data_type
*
* @return
*/
public static Element createIqCommand(JID from, JID to, final StanzaType type,
final String id, final String node, final DataType data_type) {
Element iq = createCommandEl(from, to, type, id, node, data_type);
return iq;
}
// ~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param packet
*
* @return
*/
public static Action getAction(final Packet packet) {
String action = packet.getElement().getAttribute("/iq/command", "action");
try {
return Action.valueOf(action);
} catch (Exception e) {
return Action.other;
}
}
/**
* Method description
*
*
* @param packet
* @param f_name
*
* @return
*/
public static boolean getCheckBoxFieldValue(Packet packet, String f_name) {
String result = getFieldValue(packet, f_name);
if (result == null) {
return false;
}
result = result.trim();
return result.equalsIgnoreCase("true") || result.equals("1");
}
/**
* Method description
*
*
* @param packet
*
* @return
*/
public static List<Element> getData(final Packet packet) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
return command.getChildren();
}
/**
* Method description
*
*
* @param packet
* @param el_name
* @param xmlns
*
* @return
*/
public static Element getData(final Packet packet, final String el_name,
final String xmlns) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
return command.getChild(el_name, xmlns);
}
/**
* Method description
*
*
* @param packet
* @param f_name
*
* @return
*/
public static String getFieldValue(Packet packet, String f_name) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL, XMLNS);
Element x = command.getChild("x", "jabber:x:data");
if (x != null) {
List<Element> children = x.getChildren();
if (children != null) {
for (Element child : children) {
if (child.getName().equals("field") && child.getAttribute("var").equals(f_name)) {
String value = child.getChildCData("/field/value");
if (value != null) {
return XMLUtils.unescape(value);
}
}
}
}
}
return null;
}
/**
* Method description
*
*
* @param packet
* @param f_name
* @param debug
*
* @return
*/
public static String getFieldValue(final Packet packet, final String f_name,
boolean debug) {
Element iq = packet.getElement();
log.info("Command iq: " + iq.toString());
Element command = iq.getChild(COMMAND_EL, XMLNS);
log.info("Command command: " + command.toString());
Element x = command.getChild("x", "jabber:x:data");
if (x == null) {
log.info("Command x: NULL");
return null;
}
log.info("Command x: " + x.toString());
List<Element> children = x.getChildren();
for (Element child : children) {
log.info("Command form child: " + child.toString());
if (child.getName().equals("field") && child.getAttribute("var").equals(f_name)) {
String value = child.getChildCData("/field/value");
log.info("Command found: field=" + f_name + ", value=" + value);
if (value != null) {
return XMLUtils.unescape(value);
}
} else {
log.info("Command not found: field=" + f_name + ", value="
+ child.getChildCData("/field/value"));
}
}
return null;
}
/**
* Method description
*
*
* @param packet
* @param f_name
*
* @return
*/
public static String[] getFieldValues(final Packet packet, final String f_name) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL, XMLNS);
Element x = command.getChild("x", "jabber:x:data");
if (x != null) {
List<Element> children = x.getChildren();
if (children != null) {
for (Element child : children) {
if (child.getName().equals("field") && child.getAttribute("var").equals(f_name)) {
List<String> values = new LinkedList<String>();
List<Element> val_children = child.getChildren();
for (Element val_child : val_children) {
if (val_child.getName().equals("value")) {
String value = val_child.getCData();
if (value != null) {
values.add(XMLUtils.unescape(value));
}
}
}
return values.toArray(new String[0]);
}
}
}
}
return null;
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param packet
* @param f_name
*
* @return
*/
public static boolean removeFieldValue(final Packet packet, final String f_name) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL, XMLNS);
Element x = command.getChild("x", "jabber:x:data");
if (x != null) {
List<Element> children = x.getChildren();
if (children != null) {
for (Element child : children) {
if (child.getName().equals("field") && child.getAttribute("var").equals(f_name)) {
return x.removeChild(child);
}
}
}
}
return false;
}
public static String getFieldKeyStartingWith(Packet packet, String f_name) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL, XMLNS);
Element x = command.getChild("x", "jabber:x:data");
if (x != null) {
List<Element> children = x.getChildren();
if (children != null) {
for (Element child : children) {
if (child.getName().equals("field")
&& child.getAttribute("var").startsWith(f_name)) {
return child.getAttribute("var");
}
}
}
}
return null;
}
/**
* Method description
*
*
* @param packet
* @param data
*/
public static void setData(final Packet packet, final Element data) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
command.addChild(data);
}
/**
* Method description
*
*
* @param packet
* @param data
*/
public static void setData(final Packet packet, final List<Element> data) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
command.addChildren(data);
}
/**
* Method description
*
*
* @param packet
* @param status
*/
public static void setStatus(final Packet packet, final Status status) {
Element iq = packet.getElement();
Element command = iq.getChild(COMMAND_EL);
command.setAttribute("status", status.toString());
}
// ~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param cmd
*
* @return
*/
public static Command valueof(String cmd) {
try {
return Command.valueOf(cmd);
} catch (Exception e) {
return OTHER;
} // end of try-catch
}
private static void addActionEl(Element iq, Action action) {
Element command = iq.getChild(COMMAND_EL);
Element actions = command.getChild("actions");
if (actions == null) {
actions =
new Element("actions", new String[] { Action.execute.toString() },
new String[] { action.toString() });
command.addChild(actions);
}
actions.addChild(new Element(action.toString()));
}
private static Element addDataForm(Element command, DataType data_type) {
Element x =
new Element("x", new String[] { "xmlns", "type" }, new String[] {
"jabber:x:data", data_type.name() });
command.addChild(x);
return x;
}
private static Element createCommandEl(JID from, JID to, StanzaType type, String id,
String node, DataType data_type) {
Element iq =
new Element("iq", new String[] { "type" }, new String[] { type.toString() });
if (from != null) {
iq.setAttribute("from", from.toString());
}
// The IQ packet (and command for that matter) should have the id attribute
// therefore if it is not set then NPE should be thrown.
// if (id != null) {
iq.setAttribute("id", id);
// }
if (to != null) {
iq.setAttribute("to", to.toString());
}
Element command =
new Element(COMMAND_EL, new String[] { "xmlns", "node" }, new String[] { XMLNS,
node });
iq.addChild(command);
if (data_type != null) {
addDataForm(command, data_type);
if (data_type == DataType.result) {
setStatusEl(iq, Status.completed);
}
if (data_type == DataType.form) {
setStatusEl(iq, Status.executing);
addActionEl(iq, Action.complete);
}
}
return iq;
}
private static void setStatusEl(Element iq, Status status) {
Element command = iq.getChild(COMMAND_EL);
command.setAttribute("status", status.name());
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param id
*
* @return
*/
public Packet getPacket(JID from, JID to, final StanzaType type, final String id) {
Element elem = createIqCommand(from, to, type, id, this.toString(), null);
Packet result = Packet.packetInstance(elem, from, to);
result.setPriority(priority);
return result;
}
/**
* Method description
*
*
* @param from
* @param to
* @param type
* @param id
* @param data_type
*
* @return
*/
public Packet
getPacket(JID from, JID to, StanzaType type, String id, DataType data_type) {
Element elem = createIqCommand(from, to, type, id, this.toString(), data_type);
Packet result = Packet.packetInstance(elem, from, to);
result.setPriority(priority);
return result;
}
} // Command