Home | History | Annotate | Download | only in commands
      1 /**
      2  * $RCSfile$
      3  * $Revision$
      4  * $Date$
      5  *
      6  * Copyright 2005-2008 Jive Software.
      7  *
      8  * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
      9  * you may not use this file except in compliance with the License.
     10  * You may obtain a copy of the License at
     11  *
     12  *     http://www.apache.org/licenses/LICENSE-2.0
     13  *
     14  * Unless required by applicable law or agreed to in writing, software
     15  * distributed under the License is distributed on an "AS IS" BASIS,
     16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     17  * See the License for the specific language governing permissions and
     18  * limitations under the License.
     19  */
     20 
     21 package org.jivesoftware.smackx.commands;
     22 
     23 import org.jivesoftware.smack.*;
     24 import org.jivesoftware.smack.filter.PacketFilter;
     25 import org.jivesoftware.smack.filter.PacketTypeFilter;
     26 import org.jivesoftware.smack.packet.IQ;
     27 import org.jivesoftware.smack.packet.Packet;
     28 import org.jivesoftware.smack.packet.PacketExtension;
     29 import org.jivesoftware.smack.packet.XMPPError;
     30 import org.jivesoftware.smack.util.StringUtils;
     31 import org.jivesoftware.smackx.Form;
     32 import org.jivesoftware.smackx.NodeInformationProvider;
     33 import org.jivesoftware.smackx.ServiceDiscoveryManager;
     34 import org.jivesoftware.smackx.commands.AdHocCommand.Action;
     35 import org.jivesoftware.smackx.commands.AdHocCommand.Status;
     36 import org.jivesoftware.smackx.packet.AdHocCommandData;
     37 import org.jivesoftware.smackx.packet.DiscoverInfo;
     38 import org.jivesoftware.smackx.packet.DiscoverInfo.Identity;
     39 import org.jivesoftware.smackx.packet.DiscoverItems;
     40 
     41 import java.util.ArrayList;
     42 import java.util.Collection;
     43 import java.util.Collections;
     44 import java.util.List;
     45 import java.util.Map;
     46 import java.util.WeakHashMap;
     47 import java.util.concurrent.ConcurrentHashMap;
     48 
     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 a Connection instance to
     54  * {@link #getAddHocCommandsManager(org.jivesoftware.smack.Connection)} in order to
     55  * get an instance of this class.
     56  *
     57  * @author Gabriel Guardincerri
     58  */
     59 public class AdHocCommandManager {
     60 
     61     private static final String DISCO_NAMESPACE = "http://jabber.org/protocol/commands";
     62 
     63     private static final String discoNode = DISCO_NAMESPACE;
     64 
     65     /**
     66      * The session time out in seconds.
     67      */
     68     private static final int SESSION_TIMEOUT = 2 * 60;
     69 
     70     /**
     71      * Map a Connection with it AdHocCommandManager. This map have a key-value
     72      * pair for every active connection.
     73      */
     74     private static Map<Connection, AdHocCommandManager> instances =
     75             new ConcurrentHashMap<Connection, AdHocCommandManager>();
     76 
     77     /**
     78      * Register the listener for all the connection creations. When a new
     79      * connection is created a new AdHocCommandManager is also created and
     80      * related to that connection.
     81      */
     82     static {
     83         Connection.addConnectionCreationListener(new ConnectionCreationListener() {
     84             public void connectionCreated(Connection connection) {
     85                 new AdHocCommandManager(connection);
     86             }
     87         });
     88     }
     89 
     90     /**
     91      * Returns the <code>AdHocCommandManager</code> related to the
     92      * <code>connection</code>.
     93      *
     94      * @param connection the XMPP connection.
     95      * @return the AdHocCommandManager associated with the connection.
     96      */
     97     public static AdHocCommandManager getAddHocCommandsManager(Connection connection) {
     98         return instances.get(connection);
     99     }
    100 
    101     /**
    102      * Thread that reaps stale sessions.
    103      */
    104     private Thread sessionsSweeper;
    105 
    106     /**
    107      * The Connection that this instances of AdHocCommandManager manages
    108      */
    109     private Connection connection;
    110 
    111     /**
    112      * Map a command node with its AdHocCommandInfo. Note: Key=command node,
    113      * Value=command. Command node matches the node attribute sent by command
    114      * requesters.
    115      */
    116     private Map<String, AdHocCommandInfo> commands = Collections
    117             .synchronizedMap(new WeakHashMap<String, AdHocCommandInfo>());
    118 
    119     /**
    120      * Map a command session ID with the instance LocalCommand. The LocalCommand
    121      * is the an objects that has all the information of the current state of
    122      * the command execution. Note: Key=session ID, Value=LocalCommand. Session
    123      * ID matches the sessionid attribute sent by command responders.
    124      */
    125     private Map<String, LocalCommand> executingCommands = new ConcurrentHashMap<String, LocalCommand>();
    126 
    127     private AdHocCommandManager(Connection connection) {
    128         super();
    129         this.connection = connection;
    130         init();
    131     }
    132 
    133     /**
    134      * Registers a new command with this command manager, which is related to a
    135      * connection. The <tt>node</tt> is an unique identifier of that command for
    136      * the connection related to this command manager. The <tt>name</tt> is the
    137      * human readable name of the command. The <tt>class</tt> is the class of
    138      * the command, which must extend {@link LocalCommand} and have a default
    139      * constructor.
    140      *
    141      * @param node the unique identifier of the command.
    142      * @param name the human readable name of the command.
    143      * @param clazz the class of the command, which must extend {@link LocalCommand}.
    144      */
    145     public void registerCommand(String node, String name, final Class<? extends LocalCommand> clazz) {
    146         registerCommand(node, name, new LocalCommandFactory() {
    147             public LocalCommand getInstance() throws InstantiationException, IllegalAccessException  {
    148                 return clazz.newInstance();
    149             }
    150         });
    151     }
    152 
    153     /**
    154      * Registers a new command with this command manager, which is related to a
    155      * connection. The <tt>node</tt> is an unique identifier of that
    156      * command for the connection related to this command manager. The <tt>name</tt>
    157      * is the human readeale name of the command. The <tt>factory</tt> generates
    158      * new instances of the command.
    159      *
    160      * @param node the unique identifier of the command.
    161      * @param name the human readable name of the command.
    162      * @param factory a factory to create new instances of the command.
    163      */
    164     public void registerCommand(String node, final String name, LocalCommandFactory factory) {
    165         AdHocCommandInfo commandInfo = new AdHocCommandInfo(node, name, connection.getUser(), factory);
    166 
    167         commands.put(node, commandInfo);
    168         // Set the NodeInformationProvider that will provide information about
    169         // the added command
    170         ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node,
    171                 new NodeInformationProvider() {
    172                     public List<DiscoverItems.Item> getNodeItems() {
    173                         return null;
    174                     }
    175 
    176                     public List<String> getNodeFeatures() {
    177                         List<String> answer = new ArrayList<String>();
    178                         answer.add(DISCO_NAMESPACE);
    179                         // TODO: check if this service is provided by the
    180                         // TODO: current connection.
    181                         answer.add("jabber:x:data");
    182                         return answer;
    183                     }
    184 
    185                     public List<DiscoverInfo.Identity> getNodeIdentities() {
    186                         List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>();
    187                         DiscoverInfo.Identity identity = new DiscoverInfo.Identity(
    188                                 "automation", name, "command-node");
    189                         answer.add(identity);
    190                         return answer;
    191                     }
    192 
    193                     @Override
    194                     public List<PacketExtension> getNodePacketExtensions() {
    195                         return null;
    196                     }
    197 
    198                 });
    199     }
    200 
    201     /**
    202      * Discover the commands of an specific JID. The <code>jid</code> is a
    203      * full JID.
    204      *
    205      * @param jid the full JID to retrieve the commands for.
    206      * @return the discovered items.
    207      * @throws XMPPException if the operation failed for some reason.
    208      */
    209     public DiscoverItems discoverCommands(String jid) throws XMPPException {
    210         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager
    211                 .getInstanceFor(connection);
    212         return serviceDiscoveryManager.discoverItems(jid, discoNode);
    213     }
    214 
    215     /**
    216      * Publish the commands to an specific JID.
    217      *
    218      * @param jid the full JID to publish the commands to.
    219      * @throws XMPPException if the operation failed for some reason.
    220      */
    221     public void publishCommands(String jid) throws XMPPException {
    222         ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager
    223                 .getInstanceFor(connection);
    224 
    225         // Collects the commands to publish as items
    226         DiscoverItems discoverItems = new DiscoverItems();
    227         Collection<AdHocCommandInfo> xCommandsList = getRegisteredCommands();
    228 
    229         for (AdHocCommandInfo info : xCommandsList) {
    230             DiscoverItems.Item item = new DiscoverItems.Item(info.getOwnerJID());
    231             item.setName(info.getName());
    232             item.setNode(info.getNode());
    233             discoverItems.addItem(item);
    234         }
    235 
    236         serviceDiscoveryManager.publishItems(jid, discoNode, discoverItems);
    237     }
    238 
    239     /**
    240      * Returns a command that represents an instance of a command in a remote
    241      * host. It is used to execute remote commands. The concept is similar to
    242      * RMI. Every invocation on this command is equivalent to an invocation in
    243      * the remote command.
    244      *
    245      * @param jid the full JID of the host of the remote command
    246      * @param node the identifier of the command
    247      * @return a local instance equivalent to the remote command.
    248      */
    249     public RemoteCommand getRemoteCommand(String jid, String node) {
    250         return new RemoteCommand(connection, node, jid);
    251     }
    252 
    253     /**
    254      * <ul>
    255      * <li>Adds listeners to the connection</li>
    256      * <li>Registers the ad-hoc command feature to the ServiceDiscoveryManager</li>
    257      * <li>Registers the items of the feature</li>
    258      * <li>Adds packet listeners to handle execution requests</li>
    259      * <li>Creates and start the session sweeper</li>
    260      * </ul>
    261      */
    262     private void init() {
    263         // Register the new instance and associate it with the connection
    264         instances.put(connection, this);
    265 
    266         // Add a listener to the connection that removes the registered instance
    267         // when the connection is closed
    268         connection.addConnectionListener(new ConnectionListener() {
    269             public void connectionClosed() {
    270                 // Unregister this instance since the connection has been closed
    271                 instances.remove(connection);
    272             }
    273 
    274             public void connectionClosedOnError(Exception e) {
    275                 // Unregister this instance since the connection has been closed
    276                 instances.remove(connection);
    277             }
    278 
    279             public void reconnectionSuccessful() {
    280                 // Register this instance since the connection has been
    281                 // reestablished
    282                 instances.put(connection, AdHocCommandManager.this);
    283             }
    284 
    285             public void reconnectingIn(int seconds) {
    286                 // Nothing to do
    287             }
    288 
    289             public void reconnectionFailed(Exception e) {
    290                 // Nothing to do
    291             }
    292         });
    293 
    294         // Add the feature to the service discovery manage to show that this
    295         // connection supports the AdHoc-Commands protocol.
    296         // This information will be used when another client tries to
    297         // discover whether this client supports AdHoc-Commands or not.
    298         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(
    299                 DISCO_NAMESPACE);
    300 
    301         // Set the NodeInformationProvider that will provide information about
    302         // which AdHoc-Commands are registered, whenever a disco request is
    303         // received
    304         ServiceDiscoveryManager.getInstanceFor(connection)
    305                 .setNodeInformationProvider(discoNode,
    306                         new NodeInformationProvider() {
    307                             public List<DiscoverItems.Item> getNodeItems() {
    308 
    309                                 List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>();
    310                                 Collection<AdHocCommandInfo> commandsList = getRegisteredCommands();
    311 
    312                                 for (AdHocCommandInfo info : commandsList) {
    313                                     DiscoverItems.Item item = new DiscoverItems.Item(
    314                                             info.getOwnerJID());
    315                                     item.setName(info.getName());
    316                                     item.setNode(info.getNode());
    317                                     answer.add(item);
    318                                 }
    319 
    320                                 return answer;
    321                             }
    322 
    323                             public List<String> getNodeFeatures() {
    324                                 return null;
    325                             }
    326 
    327                             public List<Identity> getNodeIdentities() {
    328                                 return null;
    329                             }
    330 
    331                             @Override
    332                             public List<PacketExtension> getNodePacketExtensions() {
    333                                 return null;
    334                             }
    335                         });
    336 
    337         // The packet listener and the filter for processing some AdHoc Commands
    338         // Packets
    339         PacketListener listener = new PacketListener() {
    340             public void processPacket(Packet packet) {
    341                 AdHocCommandData requestData = (AdHocCommandData) packet;
    342                 processAdHocCommand(requestData);
    343             }
    344         };
    345 
    346         PacketFilter filter = new PacketTypeFilter(AdHocCommandData.class);
    347         connection.addPacketListener(listener, filter);
    348 
    349         sessionsSweeper = null;
    350     }
    351 
    352     /**
    353      * Process the AdHoc-Command packet that request the execution of some
    354      * action of a command. If this is the first request, this method checks,
    355      * before executing the command, if:
    356      * <ul>
    357      *  <li>The requested command exists</li>
    358      *  <li>The requester has permissions to execute it</li>
    359      *  <li>The command has more than one stage, if so, it saves the command and
    360      *      session ID for further use</li>
    361      * </ul>
    362      *
    363      * <br>
    364      * <br>
    365      * If this is not the first request, this method checks, before executing
    366      * the command, if:
    367      * <ul>
    368      *  <li>The session ID of the request was stored</li>
    369      *  <li>The session life do not exceed the time out</li>
    370      *  <li>The action to execute is one of the available actions</li>
    371      * </ul>
    372      *
    373      * @param requestData
    374      *            the packet to process.
    375      */
    376     private void processAdHocCommand(AdHocCommandData requestData) {
    377         // Only process requests of type SET
    378         if (requestData.getType() != IQ.Type.SET) {
    379             return;
    380         }
    381 
    382         // Creates the response with the corresponding data
    383         AdHocCommandData response = new AdHocCommandData();
    384         response.setTo(requestData.getFrom());
    385         response.setPacketID(requestData.getPacketID());
    386         response.setNode(requestData.getNode());
    387         response.setId(requestData.getTo());
    388 
    389         String sessionId = requestData.getSessionID();
    390         String commandNode = requestData.getNode();
    391 
    392         if (sessionId == null) {
    393             // A new execution request has been received. Check that the
    394             // command exists
    395             if (!commands.containsKey(commandNode)) {
    396                 // Requested command does not exist so return
    397                 // item_not_found error.
    398                 respondError(response, XMPPError.Condition.item_not_found);
    399                 return;
    400             }
    401 
    402             // Create new session ID
    403             sessionId = StringUtils.randomString(15);
    404 
    405             try {
    406                 // Create a new instance of the command with the
    407                 // corresponding sessioid
    408                 LocalCommand command = newInstanceOfCmd(commandNode, sessionId);
    409 
    410                 response.setType(IQ.Type.RESULT);
    411                 command.setData(response);
    412 
    413                 // Check that the requester has enough permission.
    414                 // Answer forbidden error if requester permissions are not
    415                 // enough to execute the requested command
    416                 if (!command.hasPermission(requestData.getFrom())) {
    417                     respondError(response, XMPPError.Condition.forbidden);
    418                     return;
    419                 }
    420 
    421                 Action action = requestData.getAction();
    422 
    423                 // If the action is unknown then respond an error.
    424                 if (action != null && action.equals(Action.unknown)) {
    425                     respondError(response, XMPPError.Condition.bad_request,
    426                             AdHocCommand.SpecificErrorCondition.malformedAction);
    427                     return;
    428                 }
    429 
    430                 // If the action is not execute, then it is an invalid action.
    431                 if (action != null && !action.equals(Action.execute)) {
    432                     respondError(response, XMPPError.Condition.bad_request,
    433                             AdHocCommand.SpecificErrorCondition.badAction);
    434                     return;
    435                 }
    436 
    437                 // Increase the state number, so the command knows in witch
    438                 // stage it is
    439                 command.incrementStage();
    440                 // Executes the command
    441                 command.execute();
    442 
    443                 if (command.isLastStage()) {
    444                     // If there is only one stage then the command is completed
    445                     response.setStatus(Status.completed);
    446                 }
    447                 else {
    448                     // Else it is still executing, and is registered to be
    449                     // available for the next call
    450                     response.setStatus(Status.executing);
    451                     executingCommands.put(sessionId, command);
    452                     // See if the session reaping thread is started. If not, start it.
    453                     if (sessionsSweeper == null) {
    454                         sessionsSweeper = new Thread(new Runnable() {
    455                             public void run() {
    456                                 while (true) {
    457                                     for (String sessionId : executingCommands.keySet()) {
    458                                         LocalCommand command = executingCommands.get(sessionId);
    459                                         // Since the command could be removed in the meanwhile
    460                                         // of getting the key and getting the value - by a
    461                                         // processed packet. We must check if it still in the
    462                                         // map.
    463                                         if (command != null) {
    464                                             long creationStamp = command.getCreationDate();
    465                                             // Check if the Session data has expired (default is
    466                                             // 10 minutes)
    467                                             // To remove it from the session list it waits for
    468                                             // the double of the of time out time. This is to
    469                                             // let
    470                                             // the requester know why his execution request is
    471                                             // not accepted. If the session is removed just
    472                                             // after the time out, then whe the user request to
    473                                             // continue the execution he will recieved an
    474                                             // invalid session error and not a time out error.
    475                                             if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) {
    476                                                 // Remove the expired session
    477                                                 executingCommands.remove(sessionId);
    478                                             }
    479                                         }
    480                                     }
    481                                     try {
    482                                         Thread.sleep(1000);
    483                                     }
    484                                     catch (InterruptedException ie) {
    485                                         // Ignore.
    486                                     }
    487                                 }
    488                             }
    489 
    490                         });
    491                         sessionsSweeper.setDaemon(true);
    492                         sessionsSweeper.start();
    493                     }
    494                 }
    495 
    496                 // Sends the response packet
    497                 connection.sendPacket(response);
    498 
    499             }
    500             catch (XMPPException e) {
    501                 // If there is an exception caused by the next, complete,
    502                 // prev or cancel method, then that error is returned to the
    503                 // requester.
    504                 XMPPError error = e.getXMPPError();
    505 
    506                 // If the error type is cancel, then the execution is
    507                 // canceled therefore the status must show that, and the
    508                 // command be removed from the executing list.
    509                 if (XMPPError.Type.CANCEL.equals(error.getType())) {
    510                     response.setStatus(Status.canceled);
    511                     executingCommands.remove(sessionId);
    512                 }
    513                 respondError(response, error);
    514                 e.printStackTrace();
    515             }
    516         }
    517         else {
    518             LocalCommand command = executingCommands.get(sessionId);
    519 
    520             // Check that a command exists for the specified sessionID
    521             // This also handles if the command was removed in the meanwhile
    522             // of getting the key and the value of the map.
    523             if (command == null) {
    524                 respondError(response, XMPPError.Condition.bad_request,
    525                         AdHocCommand.SpecificErrorCondition.badSessionid);
    526                 return;
    527             }
    528 
    529             // Check if the Session data has expired (default is 10 minutes)
    530             long creationStamp = command.getCreationDate();
    531             if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) {
    532                 // Remove the expired session
    533                 executingCommands.remove(sessionId);
    534 
    535                 // Answer a not_allowed error (session-expired)
    536                 respondError(response, XMPPError.Condition.not_allowed,
    537                         AdHocCommand.SpecificErrorCondition.sessionExpired);
    538                 return;
    539             }
    540 
    541             /*
    542              * Since the requester could send two requests for the same
    543              * executing command i.e. the same session id, all the execution of
    544              * the action must be synchronized to avoid inconsistencies.
    545              */
    546             synchronized (command) {
    547                 Action action = requestData.getAction();
    548 
    549                 // If the action is unknown the respond an error
    550                 if (action != null && action.equals(Action.unknown)) {
    551                     respondError(response, XMPPError.Condition.bad_request,
    552                             AdHocCommand.SpecificErrorCondition.malformedAction);
    553                     return;
    554                 }
    555 
    556                 // If the user didn't specify an action or specify the execute
    557                 // action then follow the actual default execute action
    558                 if (action == null || Action.execute.equals(action)) {
    559                     action = command.getExecuteAction();
    560                 }
    561 
    562                 // Check that the specified action was previously
    563                 // offered
    564                 if (!command.isValidAction(action)) {
    565                     respondError(response, XMPPError.Condition.bad_request,
    566                             AdHocCommand.SpecificErrorCondition.badAction);
    567                     return;
    568                 }
    569 
    570                 try {
    571                     // TODO: Check that all the requierd fields of the form are
    572                     // TODO: filled, if not throw an exception. This will simplify the
    573                     // TODO: construction of new commands
    574 
    575                     // Since all errors were passed, the response is now a
    576                     // result
    577                     response.setType(IQ.Type.RESULT);
    578 
    579                     // Set the new data to the command.
    580                     command.setData(response);
    581 
    582                     if (Action.next.equals(action)) {
    583                         command.incrementStage();
    584                         command.next(new Form(requestData.getForm()));
    585                         if (command.isLastStage()) {
    586                             // If it is the last stage then the command is
    587                             // completed
    588                             response.setStatus(Status.completed);
    589                         }
    590                         else {
    591                             // Otherwise it is still executing
    592                             response.setStatus(Status.executing);
    593                         }
    594                     }
    595                     else if (Action.complete.equals(action)) {
    596                         command.incrementStage();
    597                         command.complete(new Form(requestData.getForm()));
    598                         response.setStatus(Status.completed);
    599                         // Remove the completed session
    600                         executingCommands.remove(sessionId);
    601                     }
    602                     else if (Action.prev.equals(action)) {
    603                         command.decrementStage();
    604                         command.prev();
    605                     }
    606                     else if (Action.cancel.equals(action)) {
    607                         command.cancel();
    608                         response.setStatus(Status.canceled);
    609                         // Remove the canceled session
    610                         executingCommands.remove(sessionId);
    611                     }
    612 
    613                     connection.sendPacket(response);
    614                 }
    615                 catch (XMPPException e) {
    616                     // If there is an exception caused by the next, complete,
    617                     // prev or cancel method, then that error is returned to the
    618                     // requester.
    619                     XMPPError error = e.getXMPPError();
    620 
    621                     // If the error type is cancel, then the execution is
    622                     // canceled therefore the status must show that, and the
    623                     // command be removed from the executing list.
    624                     if (XMPPError.Type.CANCEL.equals(error.getType())) {
    625                         response.setStatus(Status.canceled);
    626                         executingCommands.remove(sessionId);
    627                     }
    628                     respondError(response, error);
    629 
    630                     e.printStackTrace();
    631                 }
    632             }
    633         }
    634     }
    635 
    636     /**
    637      * Responds an error with an specific condition.
    638      *
    639      * @param response the response to send.
    640      * @param condition the condition of the error.
    641      */
    642     private void respondError(AdHocCommandData response,
    643             XMPPError.Condition condition) {
    644         respondError(response, new XMPPError(condition));
    645     }
    646 
    647     /**
    648      * Responds an error with an specific condition.
    649      *
    650      * @param response the response to send.
    651      * @param condition the condition of the error.
    652      * @param specificCondition the adhoc command error condition.
    653      */
    654     private void respondError(AdHocCommandData response, XMPPError.Condition condition,
    655             AdHocCommand.SpecificErrorCondition specificCondition)
    656     {
    657         XMPPError error = new XMPPError(condition);
    658         error.addExtension(new AdHocCommandData.SpecificError(specificCondition));
    659         respondError(response, error);
    660     }
    661 
    662     /**
    663      * Responds an error with an specific error.
    664      *
    665      * @param response the response to send.
    666      * @param error the error to send.
    667      */
    668     private void respondError(AdHocCommandData response, XMPPError error) {
    669         response.setType(IQ.Type.ERROR);
    670         response.setError(error);
    671         connection.sendPacket(response);
    672     }
    673 
    674     /**
    675      * Creates a new instance of a command to be used by a new execution request
    676      *
    677      * @param commandNode the command node that identifies it.
    678      * @param sessionID the session id of this execution.
    679      * @return the command instance to execute.
    680      * @throws XMPPException if there is problem creating the new instance.
    681      */
    682     private LocalCommand newInstanceOfCmd(String commandNode, String sessionID)
    683             throws XMPPException
    684     {
    685         AdHocCommandInfo commandInfo = commands.get(commandNode);
    686         LocalCommand command;
    687         try {
    688             command = (LocalCommand) commandInfo.getCommandInstance();
    689             command.setSessionID(sessionID);
    690             command.setName(commandInfo.getName());
    691             command.setNode(commandInfo.getNode());
    692         }
    693         catch (InstantiationException e) {
    694             e.printStackTrace();
    695             throw new XMPPException(new XMPPError(
    696                     XMPPError.Condition.interna_server_error));
    697         }
    698         catch (IllegalAccessException e) {
    699             e.printStackTrace();
    700             throw new XMPPException(new XMPPError(
    701                     XMPPError.Condition.interna_server_error));
    702         }
    703         return command;
    704     }
    705 
    706     /**
    707      * Returns the registered commands of this command manager, which is related
    708      * to a connection.
    709      *
    710      * @return the registered commands.
    711      */
    712     private Collection<AdHocCommandInfo> getRegisteredCommands() {
    713         return commands.values();
    714     }
    715 
    716     /**
    717      * Stores ad-hoc command information.
    718      */
    719     private static class AdHocCommandInfo {
    720 
    721         private String node;
    722         private String name;
    723         private String ownerJID;
    724         private LocalCommandFactory factory;
    725 
    726         public AdHocCommandInfo(String node, String name, String ownerJID,
    727                 LocalCommandFactory factory)
    728         {
    729             this.node = node;
    730             this.name = name;
    731             this.ownerJID = ownerJID;
    732             this.factory = factory;
    733         }
    734 
    735         public LocalCommand getCommandInstance() throws InstantiationException,
    736                 IllegalAccessException
    737         {
    738             return factory.getInstance();
    739         }
    740 
    741         public String getName() {
    742             return name;
    743         }
    744 
    745         public String getNode() {
    746             return node;
    747         }
    748 
    749         public String getOwnerJID() {
    750             return ownerJID;
    751         }
    752     }
    753 }
    754