Differences between revisions 4 and 5
Revision 4 as of 2010-01-27 11:23:41
Size: 7660
Editor: PietMolenaar
Comment:
Revision 5 as of 2010-01-27 19:02:05
Size: 8209
Comment:
Deletions are marked like this. Additions are marked like this.
Line 97: Line 97:
  * ScooterMorris: I don't think so. Those are really convenience functions for handlers and should never be referenced externally. Note that both addArgument and addDescription are protected methods...
Line 98: Line 99:
  * ScooterMorris: Not sure what you mean here -- that is certainly the goal of the CyCommandHandler interface and it's only real function (although I eventually would like to see the coreCommands as a core plugin).
Line 105: Line 106:
  * ScooterMorris: No, Tunable explicitly accepts "null" as a value to indicate that there is no initial value for this Tunable

Feature Description and Review: CyCommandHandler

Overview

  • One of the strengths of Cytoscape is it's plugin architecture and the wealth of plugins that are available. One of the weaknesses of Cytoscape is that plugins have no easy way to take advantage of functionality available in other plugins. As a result, the user must act as the "inter-plugin" communications agent, or plugin authors must use the CyAttributes mechanism to exchange data back and forth, which entails a certain level of coordination and agreement that can sometimes be difficult. The goal of this new API within Cytoscape is to partially solve this weakness by providing a mechanism through which a plugin can publish a set of functions that it will perform. Plugins that wish to make use of that functionality can do without directly accessing the API (which means you don't need to include the plugin in your classpath).

    The general approach taken by the CyCommandHandler mechanism is similar to the pattern used by CyLayouts. A plugin registers it's commands when it is loaded, and plugins can check to see if a given command has been registered. To avoid multiple plugins attempting to register the same command (e.g. 'open'), each plugin can reserve a namespace for itself. Once a plugin has reserved a namespace, no other plugin may reserve the same namespace. In this way, a plugin might reserve the IDMapper namespace and any commands registered by that plugin would be part of the IDMapper namespace. A plugin may reserve multiple namespaces, but this should be rare. Arguments to commands are given as a Map of name, value pairs or a list of Tunables. In any case, the list of possible arguments may be determined through calls to the API.

Example

Consider the example below. We have two plugins: MyPlugin and YourPlugin. Hypothetically, MyPlugin provides some sort of analysis algorithm that takes a network, a list of nodes, and an iteration parameter and returns a score and a value for each node. YourPlugin wants to use that algorithm and visualize the results in some way. Here is the code for MyPlugin:

public class MyPlugin extends CytoscapePlugin {
   public MyPlugin() {
      // Plugin initialization
      try {
        // You must reserve your namespace first
        CyCommandNamespace ns = CyCommandManager.reserveNamespace("my algorithm");

        // Now register this handler as handling "analyze"
        CyCommandHandler myHandler = new MyHandler(ns);
      } catch (RuntimeException e) {
        // Handle already registered exceptions
      }
   }

   // Use a separate class so we can extend AbstractCommandHandler
   class MyHandler extends AbstractCommandHandler {
      protected MyHandler(CyCommandNamespace ns) {
          super(ns);
          addArgument("analyze", "network", "current");
          addArgument("analyze", "nodeList");
          addArgument("analyze", "interations", new Integer(2));
      }
      public String getHandlerName() { return "analyze"; }
      public CyCommandResult execute(String command, Map<String, Object>args) 
                             throws CyCommandException {
          // Assuming we've implemented arguments as Tunables
          return execute(command, createTunableCollection(args));
      }
      public CyCommandResult execute(String command, Collection<Tunable>args)
                             throws CyCommandException {
          // Execution code goes here...
      }
   }
 }

The code for YourPlugin is even easier...

public class YourPlugin extends CytoscapePlugin {
    public YourPlugin {
        // Plugin initialization.  Note: we don't want to look for MyPlugin yet.  That should
        // wait until we actually want to use it.  This avoids errors that result from the
        // arbitrary loading order of plugins.
    }

    public void doWork() {
        Map<String, Object> args = new HashMap();
        args.put("iterations", new Integer(10));
        try {
            CyCommandResult result = CyCommandManager.execute("my algorithm", "analyze", args);
            // Visualize data from result
        } catch (CyCommandException e) {
            // Handle exception
        }
    }
}

Code Layout

All code for the CyCommands implementation is in src/cytoscape/command and is part of the cytoscape.command package. There are no hooks anywhere else in the Cytoscape core that depend on this package. The package provides two interfaces:

  • CyCommandHandler: This is the main interface for implementing a command handler. Generally, it is better to extend AbstractCommandHandler, which provides defaults for most methods and a number of convenience functions that significantly simplify implementing a new handler.

  • CyCommandNamespace: This is the namespace interface. This should only be implemented by the package itself.

In addition to the two interfaces, there are four classes:

  • AbstractCommandHandler: This is an abstract base class that provides a number of convenience routines for implementing command handlers.

  • CyCommandException: The exception that get's generated when something goes wrong with an execute.

  • CyCommandResult: The results object that is passed back. This includes any error strings, messages, and a map with the actual results.

  • CyCommandManager: The main "hook" into the Cytoscape core. This is the singleton class that holds all of the CyCommandNamespace information as well as all of the CyCommandHandlers.

And finally, there is a single command implementation for the help function: HelpCommand.

Code Review Comments

Piet

Looks good, something to look forward to for plugin developers (although this should be covered for 3.0 also). I especially like the Tunables mechanism as a model for this

  • Is there some kind of mechanism that would expose the names of the the functions also; eg. require an Enum with names describing the functionality. These could be exposed and documented through the API also...
    • ScooterMorris: There is -- you can get the names of all namespaces with CyCommandManager.getNamespaceList() and a list of all the commands for a single namespace with CyCommandManager.getCommandList(String namespace).

  • Is there some flag that can be set to check whether the plugin has been loaded? Or an event listener, fire the event when loaded...
    • ScooterMorris: No, but that shouldn't be needed. The idea is that a client would only attempt to execute a command after all initialization has completed. On the other hand, you could always listen for CYTOSCAPE_INITIALIZED if you wanted to execute a command right after initialization was complete.

Even more impressive when I studied the actual code (made my remarks above obsolete ;-) Some points about the code:

  • Should't an addArgument method be part of the interface CyCommandHandler also? And addDescription for that matter.

    • ScooterMorris: I don't think so. Those are really convenience functions for handlers and should never be referenced externally. Note that both addArgument and addDescription are protected methods...

I'm not an architect but imho the CyCommandHandler interface should be pretty strictly defined to force communication between plugins. Or am I missing something?

  • ScooterMorris: Not sure what you mean here -- that is certainly the goal of the CyCommandHandler interface and it's only real function (although I eventually would like to see the coreCommands as a core plugin).

  • In AbstractCommandHandler the value null is passed to a Tunable object. Doesn't this give rise to a nullpointer exception in Tunable?

protected void addArgument(String command, String vKey) {
                addArgument(command, vKey, null);
        }
  • ScooterMorris: No, Tunable explicitly accepts "null" as a value to indicate that there is no initial value for this Tunable

* I think you're right whereas the "help" functionality should be subsumed by the "shell"; so lift the HelpCommand out of command

CyCommandHandlers (last edited 2010-02-06 22:21:55 by ScooterMorris)

Funding for Cytoscape is provided by a federal grant from the U.S. National Institute of General Medical Sciences (NIGMS) of the Na tional Institutes of Health (NIH) under award number GM070743-01. Corporate funding is provided through a contract from Unilever PLC.

MoinMoin Appliance - Powered by TurnKey Linux