AdHocCommandManager.java

  1. /**
  2.  *
  3.  * Copyright 2005-2008 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.commands;

  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.List;
  21. import java.util.Map;
  22. import java.util.WeakHashMap;
  23. import java.util.concurrent.ConcurrentHashMap;
  24. import java.util.logging.Level;
  25. import java.util.logging.Logger;

  26. import org.jivesoftware.smack.ConnectionCreationListener;
  27. import org.jivesoftware.smack.Manager;
  28. import org.jivesoftware.smack.SmackException;
  29. import org.jivesoftware.smack.SmackException.NoResponseException;
  30. import org.jivesoftware.smack.SmackException.NotConnectedException;
  31. import org.jivesoftware.smack.XMPPConnection;
  32. import org.jivesoftware.smack.XMPPConnectionRegistry;
  33. import org.jivesoftware.smack.XMPPException;
  34. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  35. import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
  36. import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
  37. import org.jivesoftware.smack.packet.IQ;
  38. import org.jivesoftware.smack.packet.XMPPError;
  39. import org.jivesoftware.smack.util.StringUtils;
  40. import org.jivesoftware.smackx.commands.AdHocCommand.Action;
  41. import org.jivesoftware.smackx.commands.AdHocCommand.Status;
  42. import org.jivesoftware.smackx.commands.packet.AdHocCommandData;
  43. import org.jivesoftware.smackx.disco.AbstractNodeInformationProvider;
  44. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  45. import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
  46. import org.jivesoftware.smackx.disco.packet.DiscoverItems;
  47. import org.jivesoftware.smackx.xdata.Form;
  48. import org.jxmpp.jid.Jid;

  49. /**
  50.  * An AdHocCommandManager is responsible for keeping the list of available
  51.  * commands offered by a service and for processing commands requests.
  52.  *
  53.  * Pass in an XMPPConnection instance to
  54.  * {@link #getAddHocCommandsManager(org.jivesoftware.smack.XMPPConnection)} in order to
  55.  * get an instance of this class.
  56.  *
  57.  * @author Gabriel Guardincerri
  58.  */
  59. public class AdHocCommandManager extends Manager {
  60.     public static final String NAMESPACE = "http://jabber.org/protocol/commands";

  61.     private static final Logger LOGGER = Logger.getLogger(AdHocCommandManager.class.getName());

  62.     /**
  63.      * The session time out in seconds.
  64.      */
  65.     private static final int SESSION_TIMEOUT = 2 * 60;

  66.     /**
  67.      * Map an XMPPConnection with it AdHocCommandManager. This map have a key-value
  68.      * pair for every active connection.
  69.      */
  70.     private static Map<XMPPConnection, AdHocCommandManager> instances = new WeakHashMap<>();

  71.     /**
  72.      * Register the listener for all the connection creations. When a new
  73.      * connection is created a new AdHocCommandManager is also created and
  74.      * related to that connection.
  75.      */
  76.     static {
  77.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  78.             public void connectionCreated(XMPPConnection connection) {
  79.                 getAddHocCommandsManager(connection);
  80.             }
  81.         });
  82.     }

  83.     /**
  84.      * Returns the <code>AdHocCommandManager</code> related to the
  85.      * <code>connection</code>.
  86.      *
  87.      * @param connection the XMPP connection.
  88.      * @return the AdHocCommandManager associated with the connection.
  89.      */
  90.     public static synchronized AdHocCommandManager getAddHocCommandsManager(XMPPConnection connection) {
  91.         AdHocCommandManager ahcm = instances.get(connection);
  92.         if (ahcm == null) {
  93.             ahcm = new AdHocCommandManager(connection);
  94.             instances.put(connection, ahcm);
  95.         }
  96.         return ahcm;
  97.     }

  98.     /**
  99.      * Map a command node with its AdHocCommandInfo. Note: Key=command node,
  100.      * Value=command. Command node matches the node attribute sent by command
  101.      * requesters.
  102.      */
  103.     private final Map<String, AdHocCommandInfo> commands = new ConcurrentHashMap<String, AdHocCommandInfo>();

  104.     /**
  105.      * Map a command session ID with the instance LocalCommand. The LocalCommand
  106.      * is the an objects that has all the information of the current state of
  107.      * the command execution. Note: Key=session ID, Value=LocalCommand. Session
  108.      * ID matches the sessionid attribute sent by command responders.
  109.      */
  110.     private final Map<String, LocalCommand> executingCommands = new ConcurrentHashMap<String, LocalCommand>();
  111.    
  112.     private final ServiceDiscoveryManager serviceDiscoveryManager;
  113.    
  114.     /**
  115.      * Thread that reaps stale sessions.
  116.      */
  117.     // FIXME The session sweeping is horrible implemented. The thread will never stop running. A different approach must
  118.     // be implemented. For example one that does stop reaping sessions and the thread if there are no more, and restarts
  119.     // the reaping process on demand. Or for every command a scheduled task should be created that removes the session
  120.     // if it's timed out. See SMACK-624.
  121.     private Thread sessionsSweeper;

  122.     private AdHocCommandManager(XMPPConnection connection) {
  123.         super(connection);
  124.         this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);

  125.         // Add the feature to the service discovery manage to show that this
  126.         // connection supports the AdHoc-Commands protocol.
  127.         // This information will be used when another client tries to
  128.         // discover whether this client supports AdHoc-Commands or not.
  129.         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(
  130.                 NAMESPACE);

  131.         // Set the NodeInformationProvider that will provide information about
  132.         // which AdHoc-Commands are registered, whenever a disco request is
  133.         // received
  134.         ServiceDiscoveryManager.getInstanceFor(connection)
  135.                 .setNodeInformationProvider(NAMESPACE,
  136.                         new AbstractNodeInformationProvider() {
  137.                             @Override
  138.                             public List<DiscoverItems.Item> getNodeItems() {

  139.                                 List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>();
  140.                                 Collection<AdHocCommandInfo> commandsList = getRegisteredCommands();

  141.                                 for (AdHocCommandInfo info : commandsList) {
  142.                                     DiscoverItems.Item item = new DiscoverItems.Item(
  143.                                             info.getOwnerJID());
  144.                                     item.setName(info.getName());
  145.                                     item.setNode(info.getNode());
  146.                                     answer.add(item);
  147.                                 }

  148.                                 return answer;
  149.                             }
  150.                         });

  151.         // The packet listener and the filter for processing some AdHoc Commands
  152.         // Packets
  153.         connection.registerIQRequestHandler(new AbstractIqRequestHandler(AdHocCommandData.ELEMENT,
  154.                         AdHocCommandData.NAMESPACE, IQ.Type.set, Mode.async) {
  155.             @Override
  156.             public IQ handleIQRequest(IQ iqRequest) {
  157.                 AdHocCommandData requestData = (AdHocCommandData) iqRequest;
  158.                 try {
  159.                     return processAdHocCommand(requestData);
  160.                 }
  161.                 catch (InterruptedException | NoResponseException | NotConnectedException e) {
  162.                     LOGGER.log(Level.INFO, "processAdHocCommand threw exceptino", e);
  163.                     return null;
  164.                 }
  165.             }
  166.         });

  167.         sessionsSweeper = null;
  168.     }

  169.     /**
  170.      * Registers a new command with this command manager, which is related to a
  171.      * connection. The <tt>node</tt> is an unique identifier of that command for
  172.      * the connection related to this command manager. The <tt>name</tt> is the
  173.      * human readable name of the command. The <tt>class</tt> is the class of
  174.      * the command, which must extend {@link LocalCommand} and have a default
  175.      * constructor.
  176.      *
  177.      * @param node the unique identifier of the command.
  178.      * @param name the human readable name of the command.
  179.      * @param clazz the class of the command, which must extend {@link LocalCommand}.
  180.      */
  181.     public void registerCommand(String node, String name, final Class<? extends LocalCommand> clazz) {
  182.         registerCommand(node, name, new LocalCommandFactory() {
  183.             public LocalCommand getInstance() throws InstantiationException, IllegalAccessException  {
  184.                 return clazz.newInstance();
  185.             }
  186.         });
  187.     }

  188.     /**
  189.      * Registers a new command with this command manager, which is related to a
  190.      * connection. The <tt>node</tt> is an unique identifier of that
  191.      * command for the connection related to this command manager. The <tt>name</tt>
  192.      * is the human readeale name of the command. The <tt>factory</tt> generates
  193.      * new instances of the command.
  194.      *
  195.      * @param node the unique identifier of the command.
  196.      * @param name the human readable name of the command.
  197.      * @param factory a factory to create new instances of the command.
  198.      */
  199.     public void registerCommand(String node, final String name, LocalCommandFactory factory) {
  200.         AdHocCommandInfo commandInfo = new AdHocCommandInfo(node, name, connection().getUser(), factory);

  201.         commands.put(node, commandInfo);
  202.         // Set the NodeInformationProvider that will provide information about
  203.         // the added command
  204.         serviceDiscoveryManager.setNodeInformationProvider(node,
  205.                 new AbstractNodeInformationProvider() {
  206.                     @Override
  207.                     public List<String> getNodeFeatures() {
  208.                         List<String> answer = new ArrayList<String>();
  209.                         answer.add(NAMESPACE);
  210.                         // TODO: check if this service is provided by the
  211.                         // TODO: current connection.
  212.                         answer.add("jabber:x:data");
  213.                         return answer;
  214.                     }
  215.                     @Override
  216.                     public List<DiscoverInfo.Identity> getNodeIdentities() {
  217.                         List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>();
  218.                         DiscoverInfo.Identity identity = new DiscoverInfo.Identity(
  219.                                 "automation", name, "command-node");
  220.                         answer.add(identity);
  221.                         return answer;
  222.                     }
  223.                 });
  224.     }

  225.     /**
  226.      * Discover the commands of an specific JID. The <code>jid</code> is a
  227.      * full JID.
  228.      *
  229.      * @param jid the full JID to retrieve the commands for.
  230.      * @return the discovered items.
  231.      * @throws XMPPException if the operation failed for some reason.
  232.      * @throws SmackException if there was no response from the server.
  233.      * @throws InterruptedException
  234.      */
  235.     public DiscoverItems discoverCommands(Jid jid) throws XMPPException, SmackException, InterruptedException {
  236.         return serviceDiscoveryManager.discoverItems(jid, NAMESPACE);
  237.     }

  238.     /**
  239.      * Publish the commands to an specific JID.
  240.      *
  241.      * @param jid the full JID to publish the commands to.
  242.      * @throws XMPPException if the operation failed for some reason.
  243.      * @throws SmackException if there was no response from the server.
  244.      * @throws InterruptedException
  245.      */
  246.     public void publishCommands(Jid jid) throws XMPPException, SmackException, InterruptedException {
  247.         // Collects the commands to publish as items
  248.         DiscoverItems discoverItems = new DiscoverItems();
  249.         Collection<AdHocCommandInfo> xCommandsList = getRegisteredCommands();

  250.         for (AdHocCommandInfo info : xCommandsList) {
  251.             DiscoverItems.Item item = new DiscoverItems.Item(info.getOwnerJID());
  252.             item.setName(info.getName());
  253.             item.setNode(info.getNode());
  254.             discoverItems.addItem(item);
  255.         }

  256.         serviceDiscoveryManager.publishItems(jid, NAMESPACE, discoverItems);
  257.     }

  258.     /**
  259.      * Returns a command that represents an instance of a command in a remote
  260.      * host. It is used to execute remote commands. The concept is similar to
  261.      * RMI. Every invocation on this command is equivalent to an invocation in
  262.      * the remote command.
  263.      *
  264.      * @param jid the full JID of the host of the remote command
  265.      * @param node the identifier of the command
  266.      * @return a local instance equivalent to the remote command.
  267.      */
  268.     public RemoteCommand getRemoteCommand(Jid jid, String node) {
  269.         return new RemoteCommand(connection(), node, jid);
  270.     }

  271.     /**
  272.      * Process the AdHoc-Command packet that request the execution of some
  273.      * action of a command. If this is the first request, this method checks,
  274.      * before executing the command, if:
  275.      * <ul>
  276.      *  <li>The requested command exists</li>
  277.      *  <li>The requester has permissions to execute it</li>
  278.      *  <li>The command has more than one stage, if so, it saves the command and
  279.      *      session ID for further use</li>
  280.      * </ul>
  281.      *
  282.      * <br>
  283.      * <br>
  284.      * If this is not the first request, this method checks, before executing
  285.      * the command, if:
  286.      * <ul>
  287.      *  <li>The session ID of the request was stored</li>
  288.      *  <li>The session life do not exceed the time out</li>
  289.      *  <li>The action to execute is one of the available actions</li>
  290.      * </ul>
  291.      *
  292.      * @param requestData
  293.      *            the packet to process.
  294.      * @throws NotConnectedException
  295.      * @throws NoResponseException
  296.      * @throws InterruptedException
  297.      */
  298.     private IQ processAdHocCommand(AdHocCommandData requestData) throws NoResponseException, NotConnectedException, InterruptedException {
  299.         // Creates the response with the corresponding data
  300.         AdHocCommandData response = new AdHocCommandData();
  301.         response.setTo(requestData.getFrom());
  302.         response.setStanzaId(requestData.getStanzaId());
  303.         response.setNode(requestData.getNode());
  304.         response.setId(requestData.getTo());

  305.         String sessionId = requestData.getSessionID();
  306.         String commandNode = requestData.getNode();

  307.         if (sessionId == null) {
  308.             // A new execution request has been received. Check that the
  309.             // command exists
  310.             if (!commands.containsKey(commandNode)) {
  311.                 // Requested command does not exist so return
  312.                 // item_not_found error.
  313.                 return respondError(response, XMPPError.Condition.item_not_found);
  314.             }

  315.             // Create new session ID
  316.             sessionId = StringUtils.randomString(15);

  317.             try {
  318.                 // Create a new instance of the command with the
  319.                 // corresponding sessioid
  320.                 LocalCommand command = newInstanceOfCmd(commandNode, sessionId);

  321.                 response.setType(IQ.Type.result);
  322.                 command.setData(response);

  323.                 // Check that the requester has enough permission.
  324.                 // Answer forbidden error if requester permissions are not
  325.                 // enough to execute the requested command
  326.                 if (!command.hasPermission(requestData.getFrom())) {
  327.                     return respondError(response, XMPPError.Condition.forbidden);
  328.                 }

  329.                 Action action = requestData.getAction();

  330.                 // If the action is unknown then respond an error.
  331.                 if (action != null && action.equals(Action.unknown)) {
  332.                     return respondError(response, XMPPError.Condition.bad_request,
  333.                             AdHocCommand.SpecificErrorCondition.malformedAction);
  334.                 }

  335.                 // If the action is not execute, then it is an invalid action.
  336.                 if (action != null && !action.equals(Action.execute)) {
  337.                     return respondError(response, XMPPError.Condition.bad_request,
  338.                             AdHocCommand.SpecificErrorCondition.badAction);
  339.                 }

  340.                 // Increase the state number, so the command knows in witch
  341.                 // stage it is
  342.                 command.incrementStage();
  343.                 // Executes the command
  344.                 command.execute();

  345.                 if (command.isLastStage()) {
  346.                     // If there is only one stage then the command is completed
  347.                     response.setStatus(Status.completed);
  348.                 }
  349.                 else {
  350.                     // Else it is still executing, and is registered to be
  351.                     // available for the next call
  352.                     response.setStatus(Status.executing);
  353.                     executingCommands.put(sessionId, command);
  354.                     // See if the session reaping thread is started. If not, start it.
  355.                     if (sessionsSweeper == null) {
  356.                         sessionsSweeper = new Thread(new Runnable() {
  357.                             public void run() {
  358.                                 while (true) {
  359.                                     for (String sessionId : executingCommands.keySet()) {
  360.                                         LocalCommand command = executingCommands.get(sessionId);
  361.                                         // Since the command could be removed in the meanwhile
  362.                                         // of getting the key and getting the value - by a
  363.                                         // processed packet. We must check if it still in the
  364.                                         // map.
  365.                                         if (command != null) {
  366.                                             long creationStamp = command.getCreationDate();
  367.                                             // Check if the Session data has expired (default is
  368.                                             // 10 minutes)
  369.                                             // To remove it from the session list it waits for
  370.                                             // the double of the of time out time. This is to
  371.                                             // let
  372.                                             // the requester know why his execution request is
  373.                                             // not accepted. If the session is removed just
  374.                                             // after the time out, then whe the user request to
  375.                                             // continue the execution he will recieved an
  376.                                             // invalid session error and not a time out error.
  377.                                             if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) {
  378.                                                 // Remove the expired session
  379.                                                 executingCommands.remove(sessionId);
  380.                                             }
  381.                                         }
  382.                                     }
  383.                                     try {
  384.                                         Thread.sleep(1000);
  385.                                     }
  386.                                     catch (InterruptedException ie) {
  387.                                         // Ignore.
  388.                                     }
  389.                                 }
  390.                             }

  391.                         });
  392.                         sessionsSweeper.setDaemon(true);
  393.                         sessionsSweeper.start();
  394.                     }
  395.                 }

  396.                 // Sends the response packet
  397.                 return response;

  398.             }
  399.             catch (XMPPErrorException e) {
  400.                 // If there is an exception caused by the next, complete,
  401.                 // prev or cancel method, then that error is returned to the
  402.                 // requester.
  403.                 XMPPError error = e.getXMPPError();

  404.                 // If the error type is cancel, then the execution is
  405.                 // canceled therefore the status must show that, and the
  406.                 // command be removed from the executing list.
  407.                 if (XMPPError.Type.CANCEL.equals(error.getType())) {
  408.                     response.setStatus(Status.canceled);
  409.                     executingCommands.remove(sessionId);
  410.                 }
  411.                 return respondError(response, error);
  412.             }
  413.         }
  414.         else {
  415.             LocalCommand command = executingCommands.get(sessionId);

  416.             // Check that a command exists for the specified sessionID
  417.             // This also handles if the command was removed in the meanwhile
  418.             // of getting the key and the value of the map.
  419.             if (command == null) {
  420.                 return respondError(response, XMPPError.Condition.bad_request,
  421.                         AdHocCommand.SpecificErrorCondition.badSessionid);
  422.             }

  423.             // Check if the Session data has expired (default is 10 minutes)
  424.             long creationStamp = command.getCreationDate();
  425.             if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) {
  426.                 // Remove the expired session
  427.                 executingCommands.remove(sessionId);

  428.                 // Answer a not_allowed error (session-expired)
  429.                 return respondError(response, XMPPError.Condition.not_allowed,
  430.                         AdHocCommand.SpecificErrorCondition.sessionExpired);
  431.             }

  432.             /*
  433.              * Since the requester could send two requests for the same
  434.              * executing command i.e. the same session id, all the execution of
  435.              * the action must be synchronized to avoid inconsistencies.
  436.              */
  437.             synchronized (command) {
  438.                 Action action = requestData.getAction();

  439.                 // If the action is unknown the respond an error
  440.                 if (action != null && action.equals(Action.unknown)) {
  441.                     return respondError(response, XMPPError.Condition.bad_request,
  442.                             AdHocCommand.SpecificErrorCondition.malformedAction);
  443.                 }

  444.                 // If the user didn't specify an action or specify the execute
  445.                 // action then follow the actual default execute action
  446.                 if (action == null || Action.execute.equals(action)) {
  447.                     action = command.getExecuteAction();
  448.                 }

  449.                 // Check that the specified action was previously
  450.                 // offered
  451.                 if (!command.isValidAction(action)) {
  452.                     return respondError(response, XMPPError.Condition.bad_request,
  453.                             AdHocCommand.SpecificErrorCondition.badAction);
  454.                 }

  455.                 try {
  456.                     // TODO: Check that all the required fields of the form are
  457.                     // TODO: filled, if not throw an exception. This will simplify the
  458.                     // TODO: construction of new commands

  459.                     // Since all errors were passed, the response is now a
  460.                     // result
  461.                     response.setType(IQ.Type.result);

  462.                     // Set the new data to the command.
  463.                     command.setData(response);

  464.                     if (Action.next.equals(action)) {
  465.                         command.incrementStage();
  466.                         command.next(new Form(requestData.getForm()));
  467.                         if (command.isLastStage()) {
  468.                             // If it is the last stage then the command is
  469.                             // completed
  470.                             response.setStatus(Status.completed);
  471.                         }
  472.                         else {
  473.                             // Otherwise it is still executing
  474.                             response.setStatus(Status.executing);
  475.                         }
  476.                     }
  477.                     else if (Action.complete.equals(action)) {
  478.                         command.incrementStage();
  479.                         command.complete(new Form(requestData.getForm()));
  480.                         response.setStatus(Status.completed);
  481.                         // Remove the completed session
  482.                         executingCommands.remove(sessionId);
  483.                     }
  484.                     else if (Action.prev.equals(action)) {
  485.                         command.decrementStage();
  486.                         command.prev();
  487.                     }
  488.                     else if (Action.cancel.equals(action)) {
  489.                         command.cancel();
  490.                         response.setStatus(Status.canceled);
  491.                         // Remove the canceled session
  492.                         executingCommands.remove(sessionId);
  493.                     }

  494.                     return response;
  495.                 }
  496.                 catch (XMPPErrorException e) {
  497.                     // If there is an exception caused by the next, complete,
  498.                     // prev or cancel method, then that error is returned to the
  499.                     // requester.
  500.                     XMPPError error = e.getXMPPError();

  501.                     // If the error type is cancel, then the execution is
  502.                     // canceled therefore the status must show that, and the
  503.                     // command be removed from the executing list.
  504.                     if (XMPPError.Type.CANCEL.equals(error.getType())) {
  505.                         response.setStatus(Status.canceled);
  506.                         executingCommands.remove(sessionId);
  507.                     }
  508.                     return respondError(response, error);
  509.                 }
  510.             }
  511.         }
  512.     }

  513.     /**
  514.      * Responds an error with an specific condition.
  515.      *
  516.      * @param response the response to send.
  517.      * @param condition the condition of the error.
  518.      * @throws NotConnectedException
  519.      */
  520.     private IQ respondError(AdHocCommandData response,
  521.             XMPPError.Condition condition) {
  522.         return respondError(response, new XMPPError(condition));
  523.     }

  524.     /**
  525.      * Responds an error with an specific condition.
  526.      *
  527.      * @param response the response to send.
  528.      * @param condition the condition of the error.
  529.      * @param specificCondition the adhoc command error condition.
  530.      * @throws NotConnectedException
  531.      */
  532.     private static IQ respondError(AdHocCommandData response, XMPPError.Condition condition,
  533.             AdHocCommand.SpecificErrorCondition specificCondition)
  534.     {
  535.         XMPPError error = new XMPPError(condition, new AdHocCommandData.SpecificError(specificCondition));
  536.         return respondError(response, error);
  537.     }

  538.     /**
  539.      * Responds an error with an specific error.
  540.      *
  541.      * @param response the response to send.
  542.      * @param error the error to send.
  543.      * @throws NotConnectedException
  544.      */
  545.     private static IQ respondError(AdHocCommandData response, XMPPError error) {
  546.         response.setType(IQ.Type.error);
  547.         response.setError(error);
  548.         return response;
  549.     }

  550.     /**
  551.      * Creates a new instance of a command to be used by a new execution request
  552.      *
  553.      * @param commandNode the command node that identifies it.
  554.      * @param sessionID the session id of this execution.
  555.      * @return the command instance to execute.
  556.      * @throws XMPPErrorException if there is problem creating the new instance.
  557.      */
  558.     private LocalCommand newInstanceOfCmd(String commandNode, String sessionID) throws XMPPErrorException
  559.     {
  560.         AdHocCommandInfo commandInfo = commands.get(commandNode);
  561.         LocalCommand command;
  562.         try {
  563.             command = (LocalCommand) commandInfo.getCommandInstance();
  564.             command.setSessionID(sessionID);
  565.             command.setName(commandInfo.getName());
  566.             command.setNode(commandInfo.getNode());
  567.         }
  568.         catch (InstantiationException e) {
  569.             throw new XMPPErrorException(new XMPPError(
  570.                     XMPPError.Condition.internal_server_error));
  571.         }
  572.         catch (IllegalAccessException e) {
  573.             throw new XMPPErrorException(new XMPPError(
  574.                     XMPPError.Condition.internal_server_error));
  575.         }
  576.         return command;
  577.     }

  578.     /**
  579.      * Returns the registered commands of this command manager, which is related
  580.      * to a connection.
  581.      *
  582.      * @return the registered commands.
  583.      */
  584.     private Collection<AdHocCommandInfo> getRegisteredCommands() {
  585.         return commands.values();
  586.     }

  587.     /**
  588.      * Stores ad-hoc command information.
  589.      */
  590.     private static class AdHocCommandInfo {

  591.         private String node;
  592.         private String name;
  593.         private final Jid ownerJID;
  594.         private LocalCommandFactory factory;

  595.         public AdHocCommandInfo(String node, String name, Jid ownerJID,
  596.                 LocalCommandFactory factory)
  597.         {
  598.             this.node = node;
  599.             this.name = name;
  600.             this.ownerJID = ownerJID;
  601.             this.factory = factory;
  602.         }

  603.         public LocalCommand getCommandInstance() throws InstantiationException,
  604.                 IllegalAccessException
  605.         {
  606.             return factory.getInstance();
  607.         }

  608.         public String getName() {
  609.             return name;
  610.         }

  611.         public String getNode() {
  612.             return node;
  613.         }

  614.         public Jid getOwnerJID() {
  615.             return ownerJID;
  616.         }
  617.     }
  618. }