Commit 0f0b2cb4 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Refactoring work. JM-609

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@3636 b35dd754-fafc-0310-a699-88a17e54d16e
parent 99230e51
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
package org.jivesoftware.wildfire.commands; package org.jivesoftware.wildfire.commands;
import org.dom4j.Element; import org.dom4j.Element;
import org.xmpp.packet.JID;
import org.jivesoftware.wildfire.XMPPServer; import org.jivesoftware.wildfire.XMPPServer;
import org.xmpp.packet.JID;
import java.util.List; import java.util.List;
...@@ -107,9 +107,10 @@ public abstract class AdHocCommand { ...@@ -107,9 +107,10 @@ public abstract class AdHocCommand {
* *
* @param data the gathered data through the command stages or <tt>null</tt> if the * @param data the gathered data through the command stages or <tt>null</tt> if the
* command does not have stages. * command does not have stages.
* @return a reported data or note element with the answer of the execution. * @param command the command element to be sent to the command requester with a reported
* data result or note element with the answer of the execution.
*/ */
public abstract Element execute(SessionData data); public abstract void execute(SessionData data, Element command);
/** /**
* Adds to the command element the data form or notes required by the current stage. The * Adds to the command element the data form or notes required by the current stage. The
...@@ -154,9 +155,7 @@ public abstract class AdHocCommand { ...@@ -154,9 +155,7 @@ public abstract class AdHocCommand {
* Increments the stage number by one and adds to the command element the new data form and * Increments the stage number by one and adds to the command element the new data form and
* new allowed actions that the user might perform. * new allowed actions that the user might perform.
* *
* @param data the gathered data through the command stages or <tt>null</tt> if the * @param data the gathered data through the command stages.
* command does not have stages or the requester is requesting the execution for the
* first time.
* @param command the command element to be sent to the command requester. * @param command the command element to be sent to the command requester.
*/ */
public void addNextStageInformation(SessionData data, Element command) { public void addNextStageInformation(SessionData data, Element command) {
...@@ -171,12 +170,10 @@ public abstract class AdHocCommand { ...@@ -171,12 +170,10 @@ public abstract class AdHocCommand {
} }
/** /**
* Decrements the stage number by one and adds to the command the data form and allowed * Decrements the stage number by one and adds to the command the data form and allowed
* actions that the user might perform of the previous stage. * actions that the user might perform of the previous stage.
* *
* @param data the gathered data through the command stages or <tt>null</tt> if the * @param data the gathered data through the command stages.
* command does not have stages or the requester is requesting the execution for the
* first time.
* @param command the command element to be sent to the command requester. * @param command the command element to be sent to the command requester.
*/ */
public void addPreviousStageInformation(SessionData data, Element command) { public void addPreviousStageInformation(SessionData data, Element command) {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
* $Revision: 3023 $ * $Revision: 3023 $
* $Date: 2005-11-02 18:00:15 -0300 (Wed, 02 Nov 2005) $ * $Date: 2005-11-02 18:00:15 -0300 (Wed, 02 Nov 2005) $
* *
* Copyright (C) 2005 Jive Software. All rights reserved. * Copyright (C) 2006 Jive Software. All rights reserved.
* *
* This software is published under the terms of the GNU Public License (GPL), * This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution. * a copy of which is included in this distribution.
...@@ -12,24 +12,16 @@ package org.jivesoftware.wildfire.commands; ...@@ -12,24 +12,16 @@ package org.jivesoftware.wildfire.commands;
import org.dom4j.DocumentHelper; import org.dom4j.DocumentHelper;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.wildfire.IQHandlerInfo; import org.jivesoftware.wildfire.IQHandlerInfo;
import org.jivesoftware.wildfire.XMPPServer; import org.jivesoftware.wildfire.XMPPServer;
import org.jivesoftware.wildfire.auth.UnauthorizedException; import org.jivesoftware.wildfire.auth.UnauthorizedException;
import org.jivesoftware.wildfire.disco.*; import org.jivesoftware.wildfire.disco.*;
import org.jivesoftware.wildfire.forms.spi.XDataFormImpl; import org.jivesoftware.wildfire.forms.spi.XDataFormImpl;
import org.jivesoftware.wildfire.handler.IQHandler; import org.jivesoftware.wildfire.handler.IQHandler;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils;
import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* An AdHocCommandHandler is responsbile for providing discoverable information about the * An AdHocCommandHandler is responsbile for providing discoverable information about the
...@@ -62,234 +54,18 @@ public class AdHocCommandHandler extends IQHandler ...@@ -62,234 +54,18 @@ public class AdHocCommandHandler extends IQHandler
private IQDiscoInfoHandler infoHandler; private IQDiscoInfoHandler infoHandler;
private IQDiscoItemsHandler itemsHandler; private IQDiscoItemsHandler itemsHandler;
/** /**
* Map that holds the offered commands by this server. Note: Key=commandCode, Value=command. * Manager that keeps the list of ad-hoc commands and processing command requests.
* commandCode matches the node attribute sent by command requesters.
*/ */
private Map<String, AdHocCommand> commands = new ConcurrentHashMap<String, AdHocCommand>(); private AdHocCommandManager manager;
/**
* Map that holds the number of command sessions of each requester.
* Note: Key=requester full's JID, Value=number of sessions
*/
private Map<String, AtomicInteger> sessionsCounter = new ConcurrentHashMap<String, AtomicInteger>();
/**
* Map that holds the command sessions. Used mainly to quickly locate a SessionData.
* Note: Key=sessionID, Value=SessionData
*/
private Map<String, SessionData> sessions = new ConcurrentHashMap<String, SessionData>();
public AdHocCommandHandler() { public AdHocCommandHandler() {
super("Ad-Hoc Commands Handler"); super("Ad-Hoc Commands Handler");
info = new IQHandlerInfo("command", NAMESPACE); info = new IQHandlerInfo("command", NAMESPACE);
manager = new AdHocCommandManager();
} }
public IQ handleIQ(IQ packet) throws UnauthorizedException { public IQ handleIQ(IQ packet) throws UnauthorizedException {
IQ reply = IQ.createResultIQ(packet); return manager.process(packet);
Element iqCommand = packet.getChildElement();
// Only packets of type SET can be processed
if (!IQ.Type.set.equals(packet.getType())) {
// Answer a bad_request error
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.bad_request);
return reply;
}
String sessionid = iqCommand.attributeValue("sessionid");
String commandCode = iqCommand.attributeValue("node");
String from = packet.getFrom().toString();
AdHocCommand command = commands.get(commandCode);
if (sessionid == null) {
// A new execution request has been received. Check that the command exists
if (command == null) {
// Requested command does not exist so return item_not_found error.
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.item_not_found);
}
else {
// Check that the requester has enough permission. Answer forbidden error if
// requester permissions are not enough to execute the requested command
if (!command.hasPermission(packet.getFrom())) {
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.forbidden);
return reply;
}
// Create new session ID
sessionid = StringUtils.randomString(15);
Element childElement = reply.setChildElement("command", NAMESPACE);
if (command.getMaxStages(null) == 0) {
// The command does not require any user interaction (returns results only)
// Execute the command and return the execution result which may be a
// data form (i.e. report data) or a note element
Element answer = command.execute(null);
childElement.addAttribute("sessionid", sessionid);
childElement.addAttribute("node", commandCode);
childElement.addAttribute("status", AdHocCommand.Status.completed.name());
// Add the execution result to the reply
childElement.add(answer);
}
else {
// The command requires user interactions (ie. has stages)
// Check that the user has not excedded the limit of allowed simultaneous
// command sessions.
AtomicInteger counter = sessionsCounter.get(from);
if (counter == null) {
synchronized (from.intern()) {
counter = sessionsCounter.get(from);
if (counter == null) {
counter = new AtomicInteger(0);
sessionsCounter.put(from, counter);
}
}
}
int limit = JiveGlobals.getIntProperty("xmpp.command.limit", 100);
if (counter.incrementAndGet() > limit) {
counter.decrementAndGet();
// Answer a not_allowed error since the user has exceeded limit. This
// checking prevents bad users from consuming all the system memory by not
// allowing them to create infinite simultaneous command sessions.
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.not_allowed);
return reply;
}
// Originate a new command session.
SessionData session = new SessionData(sessionid);
sessions.put(sessionid, session);
// Add to the child element the data form the user must complete and
// the allowed actions
command.addNextStageInformation(null, childElement);
}
}
}
else {
// An execution session already exists and the user has requested to perform a
// certain action.
String action = iqCommand.attributeValue("action");
SessionData session = sessions.get(sessionid);
// Check that a Session exists for the specified sessionID
if (session == null) {
// Answer a bad_request error (bad-sessionid)
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.bad_request);
return reply;
}
// Check if the Session data has expired (default is 10 minutes)
int timeout = JiveGlobals.getIntProperty("xmpp.command.timeout", 10 * 60 * 1000);
if (System.currentTimeMillis() - session.getCreationStamp() > timeout) {
// TODO Check all sessions that might have timed out (use another thread?)
// Remove the old session
removeSessionData(sessionid, from);
// Answer a not_allowed error (session-expired)
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.not_allowed);
return reply;
}
synchronized (sessionid.intern()) {
// Check if the user is requesting to cancel the command
if (AdHocCommand.Action.cancel.name().equals(action)) {
// User requested to cancel command execution so remove the session data
removeSessionData(sessionid, from);
// Generate a canceled confirmation response
Element childElement = reply.setChildElement("command", NAMESPACE);
childElement.addAttribute("sessionid", sessionid);
childElement.addAttribute("node", commandCode);
childElement.addAttribute("status", AdHocCommand.Status.canceled.name());
}
// If the user didn't specify an action then follow the default execute action
if (action == null || AdHocCommand.Action.execute.name().equals(action)) {
action = session.getExecuteAction().name();
}
// Check that the specified action was previously offered
if (!session.isValidAction(action)) {
// Answer a bad_request error (bad-action)
reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.bad_request);
return reply;
}
else if (AdHocCommand.Action.prev.name().equals(action)) {
// Move to the previous stage and add to the child element the data form
// the user must complete and the allowed actions of the previous stage
Element childElement = reply.setChildElement("command", NAMESPACE);
childElement.addAttribute("sessionid", sessionid);
childElement.addAttribute("node", commandCode);
childElement.addAttribute("status", AdHocCommand.Status.executing.name());
command.addPreviousStageInformation(session, childElement);
}
else if (AdHocCommand.Action.next.name().equals(action)) {
// Store the completed form in the session data
saveCompletedForm(iqCommand, session);
// Move to the next stage and add to the child element the new data form
// the user must complete and the new allowed actions
Element childElement = reply.setChildElement("command", NAMESPACE);
childElement.addAttribute("sessionid", sessionid);
childElement.addAttribute("node", commandCode);
childElement.addAttribute("status", AdHocCommand.Status.executing.name());
command.addNextStageInformation(session, childElement);
}
else if (AdHocCommand.Action.complete.name().equals(action)) {
// Store the completed form in the session data
saveCompletedForm(iqCommand, session);
// Execute the command and return the execution result which may be a
// data form (i.e. report data) or a note element
Element answer = command.execute(session);
Element childElement = reply.setChildElement("command", NAMESPACE);
childElement.addAttribute("sessionid", sessionid);
childElement.addAttribute("node", commandCode);
childElement.addAttribute("status", AdHocCommand.Status.completed.name());
// Add the execution result to the reply
childElement.add(answer);
// Command has been executed so remove the session data
removeSessionData(sessionid, from);
}
}
}
return reply;
}
/**
* Stores in the SessionData the fields and their values as specified in the completed
* data form by the user.
*
* @param iqCommand the command element containing the data form element.
* @param session the SessionData for this command execution.
*/
private void saveCompletedForm(Element iqCommand, SessionData session) {
Element formElement = iqCommand.element(QName.get("x", "jabber:x:data"));
if (formElement != null) {
// Generate a Map with the variable names and variables values
Map<String, List<String>> data = new HashMap<String, List<String>>();
DataForm dataForm = new DataForm(formElement);
for (FormField field : dataForm.getFields()) {
data.put(field.getVariable(), field.getValues());
}
// Store the variables and their values in the session data
session.addStageForm(data);
}
}
/**
* Releases the data kept for the command execution whose id is sessionid. The number of
* commands executions currently being executed by the user (full JID) will be decreased.
*
* @param sessionid id of the session that identifies this command execution.
* @param from the full JID of the command requester.
*/
private void removeSessionData(String sessionid, String from) {
sessions.remove(sessionid);
if (sessionsCounter.get(from).decrementAndGet() <= 0) {
// Remove the AtomicInteger when no commands are being executed
sessionsCounter.remove(from);
}
} }
public IQHandlerInfo getInfo() { public IQHandlerInfo getInfo() {
...@@ -325,7 +101,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -325,7 +101,7 @@ public class AdHocCommandHandler extends IQHandler
} }
else { else {
// Only include commands that the sender can execute // Only include commands that the sender can execute
AdHocCommand command = commands.get(node); AdHocCommand command = manager.getCommand(node);
return command != null && command.hasPermission(senderJID); return command != null && command.hasPermission(senderJID);
} }
} }
...@@ -337,7 +113,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -337,7 +113,7 @@ public class AdHocCommandHandler extends IQHandler
} }
else { else {
Element item; Element item;
for (AdHocCommand command : commands.values()) { for (AdHocCommand command : manager.getCommands()) {
// Only include commands that the sender can invoke (i.e. has enough permissions) // Only include commands that the sender can invoke (i.e. has enough permissions)
if (command.hasPermission(senderJID)) { if (command.hasPermission(senderJID)) {
item = DocumentHelper.createElement("item"); item = DocumentHelper.createElement("item");
...@@ -372,7 +148,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -372,7 +148,7 @@ public class AdHocCommandHandler extends IQHandler
infoHandler.removeServerNodeInfoProvider(NAMESPACE); infoHandler.removeServerNodeInfoProvider(NAMESPACE);
itemsHandler.removeServerNodeInfoProvider(NAMESPACE); itemsHandler.removeServerNodeInfoProvider(NAMESPACE);
// Stop commands // Stop commands
for (AdHocCommand command : commands.values()) { for (AdHocCommand command : manager.getCommands()) {
stopCommand(command); stopCommand(command);
} }
} }
...@@ -385,7 +161,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -385,7 +161,7 @@ public class AdHocCommandHandler extends IQHandler
* @param command the new ad-hoc command to add. * @param command the new ad-hoc command to add.
*/ */
public void addCommand(AdHocCommand command) { public void addCommand(AdHocCommand command) {
commands.put(command.getCode(), command); manager.addCommand(command);
startCommand(command); startCommand(command);
} }
...@@ -396,7 +172,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -396,7 +172,7 @@ public class AdHocCommandHandler extends IQHandler
* @param command the ad-hoc command to remove. * @param command the ad-hoc command to remove.
*/ */
public void removeCommand(AdHocCommand command) { public void removeCommand(AdHocCommand command) {
if (commands.remove(command.getCode()) != null) { if (manager.removeCommand(command)) {
stopCommand(command); stopCommand(command);
} }
} }
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
package org.jivesoftware.wildfire.commands; package org.jivesoftware.wildfire.commands;
import org.xmpp.packet.JID;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -22,11 +24,12 @@ import java.util.Map; ...@@ -22,11 +24,12 @@ import java.util.Map;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
class SessionData { public class SessionData {
private long creationStamp; private long creationStamp;
private String id; private String id;
private JID owner;
/** /**
* Map that keeps the association of variables and values obtained in each stage. * Map that keeps the association of variables and values obtained in each stage.
...@@ -47,25 +50,35 @@ class SessionData { ...@@ -47,25 +50,35 @@ class SessionData {
*/ */
private int stage; private int stage;
public SessionData(String sessionid) { SessionData(String sessionid, JID owner) {
this.id = sessionid; this.id = sessionid;
this.creationStamp = System.currentTimeMillis(); this.creationStamp = System.currentTimeMillis();
this.stage = 0; this.stage = -1;
this.owner = owner;
} }
public String getId() { public String getId() {
return id; return id;
} }
/**
* Returns the JID of the entity that is executing the command.
*
* @return the JID of the entity that is executing the command.
*/
public JID getOwner() {
return owner;
}
public long getCreationStamp() { public long getCreationStamp() {
return creationStamp; return creationStamp;
} }
public AdHocCommand.Action getExecuteAction() { AdHocCommand.Action getExecuteAction() {
return executeAction; return executeAction;
} }
public void setExecuteAction(AdHocCommand.Action executeAction) { void setExecuteAction(AdHocCommand.Action executeAction) {
this.executeAction = executeAction; this.executeAction = executeAction;
} }
...@@ -74,7 +87,7 @@ class SessionData { ...@@ -74,7 +87,7 @@ class SessionData {
* *
* @param allowedActions list of valid actions. * @param allowedActions list of valid actions.
*/ */
public void setAllowedActions(List<AdHocCommand.Action> allowedActions) { void setAllowedActions(List<AdHocCommand.Action> allowedActions) {
if (allowedActions == null) { if (allowedActions == null) {
allowedActions = new ArrayList<AdHocCommand.Action>(); allowedActions = new ArrayList<AdHocCommand.Action>();
} }
...@@ -88,7 +101,7 @@ class SessionData { ...@@ -88,7 +101,7 @@ class SessionData {
* @param actionName the name of the action to validate. * @param actionName the name of the action to validate.
* @return true if the specified action is valid in the current stage. * @return true if the specified action is valid in the current stage.
*/ */
public boolean isValidAction(String actionName) { boolean isValidAction(String actionName) {
for (AdHocCommand.Action action : allowedActions) { for (AdHocCommand.Action action : allowedActions) {
if (actionName.equals(action.name())) { if (actionName.equals(action.name())) {
return true; return true;
...@@ -97,7 +110,7 @@ class SessionData { ...@@ -97,7 +110,7 @@ class SessionData {
return false; return false;
} }
public void addStageForm(Map<String, List<String>> data) { void addStageForm(Map<String, List<String>> data) {
stagesData.put(stage, data); stagesData.put(stage, data);
} }
...@@ -108,7 +121,9 @@ class SessionData { ...@@ -108,7 +121,9 @@ class SessionData {
*/ */
public Map<String, List<String>> getData() { public Map<String, List<String>> getData() {
Map<String, List<String>> data = new HashMap<String, List<String>>(); Map<String, List<String>> data = new HashMap<String, List<String>>();
data.putAll((Map<String, List<String>>) stagesData.values()); for (Map<String, List<String>> stageData : stagesData.values()) {
data.putAll(stageData);
}
return data; return data;
} }
...@@ -132,7 +147,7 @@ class SessionData { ...@@ -132,7 +147,7 @@ class SessionData {
* *
* @param stage the current stage where the requester is located. * @param stage the current stage where the requester is located.
*/ */
public void setStage(int stage) { void setStage(int stage) {
this.stage = stage; this.stage = stage;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment