Size: 4294
Comment:
|
Size: 11288
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 1: | Line 1: |
(This page is under construction) | [[TableOfContents]] |
Line 10: | Line 10: |
* Should be manageable by Undo manager (in application layer). | * Command history should be manageable by Undo manager (in application layer). |
Line 12: | Line 12: |
==== Multiple Language Support ==== Commands should be easily accessible from dynamic (scripting) languages, including Python, Ruby, and JavaScript. |
* Developers can chain commands to develop a workflow, like UNIX shell piping. === Command Usecases === (Not finished yet...) === Multiple Language Support === Commands should be easily accessible from dynamic (scripting) languages, including Python, Ruby, and JavaScript. This will be implemented using scripting language engins running on JVM (Jruby, Rhino, and Jython). This enables users to write simple tasks as scripts. |
Line 27: | Line 33: |
* Each command will be represented as an OSGi service. * Command executes the function through the OSGi service registory. * For easy access to commands, an utility class '''''CommandService''''' will be used. |
* Each command will be registered as an '''''OSGi service'''''. * Users (developers) access commands through the '''''OSGi ServiceTracker'''''. * For easy access to commands, an utility class '''''CommandServiceManager''''' will be implemented. This is a wrapper class for OSGi !ServiceTracker. === How to Call Commands === |
Line 34: | Line 40: |
CommandService.getCommand("command_name").execute(); |
Command c = CommandService.getCommand("command_name"); Object result = c.execute( parameters ); |
Line 37: | Line 44: |
CommandService.getCommand("command_name").execute }}} We can use similar design to Felix ShellService: |
c = CommandService.getCommand("command_name") result = c.execute( parameters ) }}} We can implement similar functions Felix ShellService has: |
Line 58: | Line 66: |
Like other layers, Command Layer will be distributed as a bundle. This bundle consists of the following: 1. Command interface: All commands should implement this interface. 1. Command implementations: Collection of actual implementations. 1. Command Service Manager: (will be discussed in the next section) When plugin developer wants to add a new command to the system, the plucin bundle will depends on this. === Command Service Manager === ''Command Service Manager'' is a simple utility class to make all commands easily accessible from all parts of Cytoscape. Essentially, this is a custom version of '''''ServiceTracker''''' class. Since all commands are registered as OSGi services, command users access commands through OSGi service mechanism. However, it is better to wrap this with a simple API to hide the detail of the OSGi service mechanism. The CommandServiceManager API will be something like the following: {{{#!java public class CommandServiceManager { public static Command getCommand(String command_name) {} public static List<String> getAvailableCommand() {} } }}} This class is only for accessing (using) commands. Developers should register command implementations like other OSGi services (can be done with Spring-DM). Each command have a '''''OSGi Service Property''''' to represent that it is a Cytoscape command. This property will be used by the service tracker to filtering services. |
|
Line 61: | Line 89: |
{{{#!java public interface Command { public String getName(); public void execute(Object[] args); } }}} * Question: What's the best parameter set for ''execute()'' method? Array of String/Object? Actual implementation of the Command looks like the following: {{{#!java public class ImportGraphFileCommand extends Command { // Wrapping CytoscapeActions with Command } }}} === Exporting OSGi Service by Spring-DM === If we use one command = one OSGi service style design, we can use [http://www.springframework.org/osgi Spring Dynamic Module] for managing service import/export. |
{{{#!java public interface Command { // Getters and Setters public String getName(); public String getDescription(); public void setName(String name); public void setDescription(String description); // Arguments for commands are name-value pairs. public Map<String, Object> getParameter() throws IllegalArgumentException; public void setParameter(Map<String, Object> args) throws Exception; // Execute the command. public void execute() throws Exception; } }}} In 2.6, '''''Tunable''''' was a class to store parameters and their handlers. In 3.0, it is a Java ''Annotation''. All fields in Command classes should be annotated by this ''Tunable'', then command users can access those fields from ''Command'' interface: {{{#!java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Tunable { String description(); boolean required(); } }}} * In most cases, command developer can extend this super class. Validation of the arguments (parameters) should be done by private method implemented by the command developer. {{{#!java public abstract class AbstractCommand { protected String name; protected String description; public String getDescription() { return description; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } public Map<String, Object> getParameter() throws IllegalArgumentException { final Map<String, Object> parameter = new HashMap<String, Object>(); for (Field f : this.getClass().getDeclaredFields()) { if (f.isAnnotationPresent(Tunable.class)) { try { parameter.put(f.getName(), f.get(this)); } catch (IllegalAccessException e) { // Accessing private fields are allowed here. } } } return parameter; } public void setParameter(Map<String, Object> arg) throws Exception { for (Field f : this.getClass().getDeclaredFields()) { // See if the field is annotated as a Tunable. if (f.isAnnotationPresent(Tunable.class)) { try { Tunable a = f.getAnnotation(Tunable.class); System.out.println("We're modifying Tunable: " + f.getName() + " : " + a.description()); if (arg.containsKey(f.getName())) { // Handle the Tunables here. // Use handlers to process each data types. } else if (a.required()) { // required field is missing. Throw exception. } } catch (Exception ex) { System.out.println("Modification failed: " + f.toString()); throw ex; } } } } } }}} ==== Open Questions ==== * How the events are handled? Actual implementation of the Command looks like the following. Validation of the arguments are command writer's responsibility. Tunable-based setters and getters are type-safe, but does not include range checkers. {{{#!java public class ImportGraphFileCommand extends AbstractCommand { @Tunable(description = "Location of the network file to be imported.", required = true) private URI fileLocation; public void execute() throws Exception { if(validate()) { // execute the command } else { // Throw invalid argument exception } } private boolean validate() { // Check required fields and value ranges here. return true; } } }}} === Commands with Dependency Injection Framework === With Spring Framework, the command layer will looks like the following: * Injecting name and description. |
Line 85: | Line 220: |
<bean name="importGraphFileCommandService" class="org.cytoscape.command.impl.ImportGraphFileCommandService" /> }}} |
<context:annotation-config /> <context:component-scan base-package="org.cytoscape.command.internal" /> <!-- Inject command properties --> <bean id="importGraphFileCommand" class="org.cytoscape.command.internal.ImportGraphFileCommand"> <property name="name" value="importGraphFileCommand" /> <property name="description" value="Load network file." /> </bean> }}} * Export the command as OSGi service |
Line 90: | Line 235: |
<osgi:service auto-export="interfaces" ref="importGraphFileCommandService" /> }}} And Command Manager will be a class to holds a OSGi Service Tracker: |
<osgi:service id="importGraphFileCommandOSGiService" auto-export="interfaces" ref="importGraphFileCommand" /> }}} One of the advantages to use DI container is simpler integration test code. {{{#!java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"/META-INF/spring/bundle-context.xml"}) public class ExampleBeanIntegrationTest extends AbstractDependencyInjectionSpringContextTests { @Autowired private ImportGraphFileCommand command1; @Autowired private LayoutCommand command2; ... @Test public void testWorkflow() throws Exception { List<Object> parameter = new ArrayList<Object>(); List<Class<?>> parameterType = new ArrayList<Class<?>>(); parameter.add("testData/galFiltered.sif"); parameterType.add(String.class); List<Object> result = command1.execute(parameter, parameterType); //Do something with other commands } } }}} ===== Commad Implemented with Dynamic Languages ===== From Spring 2.5, it has a [http://static.springframework.org/spring/docs/2.5.x/reference/dynamic-language.html built-in support for dynamic languages]. By using this feature, developers can implement commands with other languages and make it available other developers. * RubyCommand.rb {{{#!ruby require 'java' class RubyCommand include org.cytoscape.command3.Command # Command detail end }}} * Spring configuration file {{{ <lang:jruby id="rubyService" script-interfaces="org.cytoscape.command3.Command" script-source="classpath:RubyCommand.rb"> <lang:property name="name" value="Command implemented by ruby." /> </lang:jruby> }}} And Command Service Manager will be a class to holds a OSGi Service Tracker: |
Line 108: | Line 311: |
public Object addingService(ServiceReference serviceReference) { | public Command addingService(ServiceReference serviceReference) { |
Line 132: | Line 335: |
=== Frameworks === * [http://www.springframework.org/ Spring Framework] * [http://www.springframework.org/osgi Spring-DM] |
Command Layer Definition
Command layer contains mechanism to make Cytoscape functions easily accessible from application layer. This layer should be separated from application layer and can be used in any mode: Desktop application, server version, and command line version.
Requirements
- Command layer should be separated from application or UI.
- Commands should be accessible from scripting (dynamic) languages.
- Command history should be manageable by Undo manager (in application layer).
- Extensible. Plugin writers and developers use Cytoscape as library can add their own commands.
- Developers can chain commands to develop a workflow, like UNIX shell piping.
Command Usecases
(Not finished yet...)
Multiple Language Support
Commands should be easily accessible from dynamic (scripting) languages, including Python, Ruby, and JavaScript. This will be implemented using scripting language engins running on JVM (Jruby, Rhino, and Jython). This enables users to write simple tasks as scripts.
Functions Encapsulated as Command
In general, most of the classes in cytoscape.actions will be converted into Commands. In addition, some of the methods under the class cytoscape.Cytoscape will be encapsulated as command. Such as:
- createNetwork()
- createNewSession()
- getNetwork()
Design
- attachment:commandLayer1.png
Each command will be registered as an OSGi service.
Users (developers) access commands through the OSGi ServiceTracker.
For easy access to commands, an utility class CommandServiceManager will be implemented. This is a wrapper class for OSGi ServiceTracker.
How to Call Commands
1 // Java
2 Command c = CommandService.getCommand("command_name");
3 Object result = c.execute( parameters );
4
5 // Ruby
6 c = CommandService.getCommand("command_name")
7 result = c.execute( parameters )
8
We can implement similar functions Felix ShellService has:
1 package org.apache.felix.shell;
2
3 public interface ShellService
4 {
5 public String[] getCommands();
6 public String getCommandUsage(String name);
7 public String getCommandDescription(String name);
8 public ServiceReference getCommandReference(String name);
9 public void executeCommand(
10 String commandLine, PrintStream out, PrintStream err)
11 throws Exception;
12 }
13
Like other layers, Command Layer will be distributed as a bundle. This bundle consists of the following:
- Command interface: All commands should implement this interface.
- Command implementations: Collection of actual implementations.
- Command Service Manager: (will be discussed in the next section)
When plugin developer wants to add a new command to the system, the plucin bundle will depends on this.
Command Service Manager
Command Service Manager is a simple utility class to make all commands easily accessible from all parts of Cytoscape. Essentially, this is a custom version of ServiceTracker class. Since all commands are registered as OSGi services, command users access commands through OSGi service mechanism. However, it is better to wrap this with a simple API to hide the detail of the OSGi service mechanism. The CommandServiceManager API will be something like the following:
1 public class CommandServiceManager {
2 public static Command getCommand(String command_name) {}
3 public static List<String> getAvailableCommand() {}
4 }
5
This class is only for accessing (using) commands. Developers should register command implementations like other OSGi services (can be done with Spring-DM). Each command have a OSGi Service Property to represent that it is a Cytoscape command. This property will be used by the service tracker to filtering services.
Implementation
All commands should implement the following interface:
1 public interface Command {
2 // Getters and Setters
3 public String getName();
4 public String getDescription();
5
6 public void setName(String name);
7 public void setDescription(String description);
8
9 // Arguments for commands are name-value pairs.
10 public Map<String, Object> getParameter() throws IllegalArgumentException;
11 public void setParameter(Map<String, Object> args) throws Exception;
12
13 // Execute the command.
14 public void execute() throws Exception;
15 }
16
In 2.6, Tunable was a class to store parameters and their handlers. In 3.0, it is a Java Annotation. All fields in Command classes should be annotated by this Tunable, then command users can access those fields from Command interface:
1 @Retention(RetentionPolicy.RUNTIME)
2 @Target(ElementType.FIELD)
3 public @interface Tunable {
4 String description();
5 boolean required();
6 }
7
- In most cases, command developer can extend this super class. Validation of the arguments (parameters) should be done by private method implemented by the command developer.
1 public abstract class AbstractCommand {
2 protected String name;
3 protected String description;
4
5 public String getDescription() {
6 return description;
7 }
8
9 public String getName() {
10 return name;
11 }
12
13 public void setName(String name) {
14 this.name = name;
15 }
16
17 public void setDescription(String description) {
18 this.description = description;
19 }
20
21 public Map<String, Object> getParameter() throws IllegalArgumentException {
22
23 final Map<String, Object> parameter = new HashMap<String, Object>();
24
25 for (Field f : this.getClass().getDeclaredFields()) {
26 if (f.isAnnotationPresent(Tunable.class)) {
27 try {
28 parameter.put(f.getName(), f.get(this));
29 } catch (IllegalAccessException e) {
30 // Accessing private fields are allowed here.
31 }
32 }
33 }
34 return parameter;
35 }
36
37 public void setParameter(Map<String, Object> arg) throws Exception {
38
39 for (Field f : this.getClass().getDeclaredFields()) {
40
41 // See if the field is annotated as a Tunable.
42 if (f.isAnnotationPresent(Tunable.class)) {
43 try {
44 Tunable a = f.getAnnotation(Tunable.class);
45 System.out.println("We're modifying Tunable: "
46 + f.getName() + " : " + a.description());
47 if (arg.containsKey(f.getName())) {
48
49 // Handle the Tunables here.
50 // Use handlers to process each data types.
51
52 } else if (a.required()) {
53 // required field is missing. Throw exception.
54 }
55
56 } catch (Exception ex) {
57 System.out.println("Modification failed: " + f.toString());
58 throw ex;
59 }
60 }
61 }
62
63 }
64 }
65
Open Questions
- How the events are handled?
Actual implementation of the Command looks like the following. Validation of the arguments are command writer's responsibility. Tunable-based setters and getters are type-safe, but does not include range checkers.
1 public class ImportGraphFileCommand extends AbstractCommand {
2
3 @Tunable(description = "Location of the network file to be imported.", required = true)
4 private URI fileLocation;
5
6 public void execute() throws Exception {
7 if(validate()) {
8 // execute the command
9 } else {
10 // Throw invalid argument exception
11 }
12 }
13
14 private boolean validate() {
15 // Check required fields and value ranges here.
16 return true;
17 }
18 }
19
Commands with Dependency Injection Framework
With Spring Framework, the command layer will looks like the following:
- Injecting name and description.
<context:annotation-config /> <context:component-scan base-package="org.cytoscape.command.internal" /> <!-- Inject command properties --> <bean id="importGraphFileCommand" class="org.cytoscape.command.internal.ImportGraphFileCommand"> <property name="name" value="importGraphFileCommand" /> <property name="description" value="Load network file." /> </bean>
- Export the command as OSGi service
<osgi:service id="importGraphFileCommandOSGiService" auto-export="interfaces" ref="importGraphFileCommand" />
One of the advantages to use DI container is simpler integration test code.
1 @RunWith(SpringJUnit4ClassRunner.class)
2 @ContextConfiguration(locations={"/META-INF/spring/bundle-context.xml"})
3 public class ExampleBeanIntegrationTest extends
4 AbstractDependencyInjectionSpringContextTests {
5
6 @Autowired
7 private ImportGraphFileCommand command1;
8
9 @Autowired
10 private LayoutCommand command2;
11
12 ...
13
14 @Test
15 public void testWorkflow() throws Exception {
16 List<Object> parameter = new ArrayList<Object>();
17 List<Class<?>> parameterType = new ArrayList<Class<?>>();
18
19 parameter.add("testData/galFiltered.sif");
20 parameterType.add(String.class);
21
22 List<Object> result = command1.execute(parameter, parameterType);
23
24 //Do something with other commands
25
26 }
27 }
28
Commad Implemented with Dynamic Languages
From Spring 2.5, it has a [http://static.springframework.org/spring/docs/2.5.x/reference/dynamic-language.html built-in support for dynamic languages]. By using this feature, developers can implement commands with other languages and make it available other developers.
RubyCommand.rb
require 'java' class RubyCommand include org.cytoscape.command3.Command # Command detail end
- Spring configuration file
<lang:jruby id="rubyService" script-interfaces="org.cytoscape.command3.Command" script-source="classpath:RubyCommand.rb"> <lang:property name="name" value="Command implemented by ruby." /> </lang:jruby>
And Command Service Manager will be a class to holds a OSGi Service Tracker:
1 public final class CommandManager implements BundleActivator {
2
3 private ServiceTracker commandServiceTracker;
4
5 public Command getCommand(final String commandName){
6 //returns the command using Service Tracker
7 }
8
9 public void start(BundleContext bundleContext) throws Exception {
10 commandServiceTracker = new ServiceTracker(bundleContext, Command.class
11 .getName(), null) {
12 @Override
13 public Command addingService(ServiceReference serviceReference) {
14 Command command = (Command) super.addingService(serviceReference);
15 return command;
16 }
17 };
18
19 commandServiceTracker.open();
20
21 }
22
23 public void stop(BundleContext bundleContext) throws Exception {
24 commandServiceTracker.close();
25 }
26 }
27
References and External Links
OSGi Service
[http://neilbartlett.name/downloads/preview_intro_services.pdf Introduction to Services]
[http://www.knopflerfish.org/osgi_service_tutorial.html OSGi Service Tutorial]
[http://felix.apache.org/site/apache-felix-shell-service.html Felix ShellService]
Frameworks
[http://www.springframework.org/ Spring Framework]
[http://www.springframework.org/osgi Spring-DM]
Dynamic Language Support on JVM
[http://jruby.codehaus.org/ JRuby]
[http://www.mozilla.org/rhino/ Rhino (JavaScript)]