24658
Comment:
|
37682
|
Deletions are marked like this. | Additions are marked like this. |
Line 7: | Line 7: |
* This page should be updated for new tools. - Kei (8/16/2010) |
|
Line 25: | Line 27: |
(insert graph here) | {{attachment:deps.png}} |
Line 234: | Line 236: |
== How to Use Logging for Debugging and Non-interruptive User Messages == Cytoscape provides a logging system for its bundles. Utilizing this logging system provides the following benefits: 1. Ability to issue non-interruptive user messages. During the execution of a piece of code, developers may need to communicate ''useful information to the user that does not require immediate attention''. Messages must not be incomprehensible for a typical user. A recoverable parse error, which describes the nature of the problem and its location, is an example of a non-interruptive user message. The most recent non-interruptive user message is presented in Cytoscape's status bar. Users can review all user messages in the Console. 1. Developers can issue debug messages. They are ''only useful and informative for developers''. These debug messages are then made available in the Console upon clicking the "Show all Messages" checkbox at the bottom of the Console. Developers can search through the log by entering a regular expression at the top of the Console and by choosing a specific log on the right of the Console. === Setting up Logging === Before using Cytoscape's logging system, include the following dependency in the bundle's pom.xml file: {{{ <dependency> <groupId>org.ops4j.pax.logging</groupId> <artifactId>pax-logging-api</artifactId> <version>1.5.2</version> </dependency> }}} The above dependency provides access to the pax-logging-api package. === SLF4J === Cytoscape employs the SLF4J logging API. While the pax-logging-api package allows one to use other logging APIs, like Apache log4j or Tomcat Juli, it is highly discouraged to use any other API besides SLF4J. At the beginning of a Java source file, include the following interfaces: {{{ import org.slf4j.LoggerFactory; import org.slf4j.Logger; }}} Obtain a Logger as follows: {{{ Logger logger = LoggerFactory.getLogger(getClass()); }}} Messages issued to the above logger will be sent to the logger with the fully qualified class path of the current class. For example, if the current class is "org.blah.foo.bar.internal.MyClass", messages are sent to the logger named "org.blah.foo.bar.internal.MyClass". If one wishes to use a different logger, like "com.myproduct.some.path.AnotherClass", one can obtain that logger regardless of the class issuing the log messages: {{{ Logger logger = LoggerFactory.getLogger("com.myproduct.some.path.AnotherClass"); }}} After obtaining a logger, messages can then be issued to it: {{{ logger.error("Whoa! Looks like I got an error"); }}} Because one is issuing Strings as log messages, one can send a more detailed message: {{{ logger.error("Whoa! Looks like I got some weird values. i = " + i + ", sum = " + sum); }}} The above code would construct the String even if the logger is set to discard error messages, thus incurring an unnecessary performance cost. SLF4J addresses this problem by using curly brackets: {{{ logger.error("Whoa! Looks like I got some weird values. i = {}, sum = {}", i, sum); }}} If the above log message is discarded, there is no penalty incurred for the concatenated string. === Issuing Non-interruptive User Messages === Log messages that are only at the info or warn level are considered non-interruptive user messages. {{{ logger.info("Hey there, Cytoscape user!"); logger.warn("The user will see this in the status bar."); }}} === Issuing Debug Messages === Log messages issued at any level besides info and warn are considered debug messages. {{{ logger.debug("This will not be seen by the user unless (s)he clicks \"Show all Messages\""); }}} == How to Use Tasks == A task is a unit of work to be executed asynchronously. It is particularly useful for executing pieces of code that take a fair amount of time to complete and for allowing the programmer to communicate its status and progress to the user. Specifically, tasks are just like threads yet offer additional functionality: * A task has a user interface that displays its state and progress. * Users can request tasks to be cancelled. The task is then informed that the user has requested cancellation. * If a fatal error occurs during the execution of a task, the user is appropriately informed, possibly with information to remedy the problem. === Setting Up === Before using tasks, it is necessary to specify a dependency to the Work API bundle. In the bundle's pom.xml file, one adds the following dependency: {{{ <dependency> <groupId>org.cytoscape</groupId> <artifactId>work-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> }}} === A Preliminary Example === A simple yet computationally time-consuming task of calculating Pi to an arbitrary precision is presented here as an introduction. The following series is a means to calculate Pi: Pi = 4 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11 ... Computation of the series can be formulated in Java as follows, where the variable {{{iterations}}} specifies how many iterations of the above series needs to be computed: {{{ double sum = 4.0; boolean negative = true; for (long i = 1; i < iterations; i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } return sum; }}} === Formulating a Task === The action or the algorithm of the task is wrapped in a class that implements the {{{Task}}} interface. The {{{Task}}} interface requires the following methods to be implemented: 1. {{{public void run(TaskMonitor taskMonitor) throws Exception;}}} This method is called when the task begins execution. The programmer can communicate the status and progress of a task through the {{{TaskMonitor}}} object, which will be discussed later. The task can throw an exception when an unrecoverable error occurs. 1. {{{public void cancel();}}} This method is called when the user has decided to cancel the task. Unlike the {{{run}}} method, {{{cancel}}} may ''not'' throw an uncaught exception. The Pi calculating algorithm described above can be inserted into the run method as follows: {{{ import org.cytoscape.work.Task; import org.cytoscape.work.TaskMonitor; public class PiCalculator implements Task { private final int iterations; public PiCalculator(int iterations) { this.iterations = iterations; } protected double sum = 4.0; public void run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; i < iterations; i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } } public void cancel() { } public double getSum() { return sum; } } }}} Notice {{{throws Exception}}} was not included above in the declaration of the {{{run}}} method. If the {{{run}}} method does not throw any uncaught exceptions, {{{throws Exception}}} is not necessary. The task above, however, is quite impoverished. It ignores the user when (s)he wishes to cancel the task. If the user decides to cancel the task, it will continue to execute until {{{run}}} completes. Moreover, it doesn't inform the user about its progress or status. The following sections describe how to write a more well behaving, user friendly task. === Cancelling Tasks === The fundamental issue when considering cancellation is how the run() method is informed to prematurely finish its work. Writing a task that cancels cleanly requires some careful consideration, because the {{{cancel}}} method is executed in a thread separate from the one executing {{{run}}}. The Task API does not coerce tasks to finish. All properly written tasks ought to respond to cancellations quickly. In the example above, one can add a boolean member to {{{PiCalculator}}}} that serves as a flag that is activated whenever the user decides to cancel the task. The {{{run}}} method can then check this member in its {{{for}}} loop, and if the member is flagged, it will terminate prematurely. This can be achieved as follows: {{{ private boolean stop = false; public void run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } } public void cancel() { stop = true; } }}} Now when the user cancels the task, 1. the {{{cancel}}} method is invoked, 1. the {{{stop}}} member is flagged, 1. in its following iteration, the {{{for}}} loop in {{{run}}} checks the {{{stop}}} member, 1. and the {{{for}}} loop stops, ending the {{{run}}} method. === Using the TaskMonitor === Looking at the run method, tasks are provided a {{{TaskMonitor}}}. {{{TaskMonitor}}}s allow the programmer to communicate the status and progress of a task to the user. {{{TaskMonitor}}} has the following methods: 1. {{{setTitle}}} - one provides a descriptive phrase specifying the overall goal of the task; this should be one of the first things called in the {{{run}}} method and should only be called once 1. {{{setProgress}}} - one gives a value from 0.0 to 1.0 to quantify the overall progress of the task; this should be called in main loops of a task 1. {{{setStatusMessage}}} - one specifies what the task is currently working on; this can be called repeatedly, unlike {{{setTitle}}} The {{{run}}} method can be changed to use the {{{TaskMonitor}}} as follows: {{{ public void run(TaskMonitor taskMonitor) { taskMonitor.setTitle("Pi Calculator"); boolean negative = true; taskMonitor.setStatusMessage("Calculating " + iterations + " iterations"); for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; taskMonitor.setProgress(i / ((double) iterations)); } } }}} === Using TaskManagers === Now that writing the task is complete, how does one go about executing the task? It is done with a {{{TaskManager}}}, which will: * execute the task's {{{run}}} method in its own thread, * provide the {{{run}}} method with an instance of a {{{TaskMonitor}}}, * catch any exception thrown by {{{run}}}, * and call {{{cancel}}} when the user decides to cancel the task. Before using the {{{TaskManager}}}, one must obtain an instance of a {{{TaskManager}}} by injecting it in the bundle-context-osgi.xml file: {{{ <osgi:reference id="taskManagerRef" interface="org.cytoscape.work.TaskManager"/> }}} When one has an instance of a {{{TaskManager}}}, one can execute the {{{PiCalculator}}} task as follows: {{{ PiCalculator piCalculator = new PiCalculator(...); taskManager.execute(piCalculator); }}} {{{TaskManager}}}'s {{{execute}}} method will return once the task has started, not when it has completed. === Using ValuedTasks === The problem with the {{{Task}}} interface and {{{TaskManager}}}s is that there is no way to query the status of the task. They do not provide facilities to determine whether a task has finished or is still running. Moreover, tasks that produce some result have no means of returning it upon completion to the caller who started the task. To address these limitations, the {{{ValuedTask}}} interface and the {{{ValuedTaskExecutor}}} class is provided in order to: * determine whether the task is still running, has finished successfully, has been cancelled, or has encountered an error, * and obtain the result of the task. The {{{ValuedTask}}} interface is identical to the {{{Task}}} interface, except it allows the {{{run}}} method to return an object. The {{{PiCalculator}}} object can be reformulated according to a {{{ValuedTask}}} as follows: {{{ import org.cytoscape.work.ValuedTask; ... public class PiCalculator implements ValuedTask<Double> { ... public Double run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } return new Double(sum); } ... } }}} The problem with {{{ValuedTask}}} is that it cannot be executed by a {{{TaskManager}}}, which only accepts objects that implement {{{Task}}}. The {{{ValuedTaskExecutor}}} class addresses this problem by wrapping a {{{ValuedTask}}} object and is, in turn, acceptable to the {{{TaskManager}}}. Moreover, one can call methods in a {{{ValuedTaskExecutor}}} object to determine the status of the task. The {{{PiCalculator}}} class can be executed as follows: {{{ PiCalculator piCalculator = new PiCalculator(...); ValuedTaskExecutor<Double> piCalculatorExecutor = new ValuedTaskExecutor<Double>(piCalculator); taskManager.execute(piCalculatorExecutor); System.out.println("PiCalculator's result is: " + piCalculatorExecutor.get()); }}} |
|
Line 312: | Line 582: |
This is helpful if you wish to maintain a modified branch of Cytoscape under a local source control environment, and yet be able to synchronize with Cytoscape's svn (update / commit) from the same local directory. Find additional information in [[http://svk.bestpractical.com/view/HomePage|SVK home page]] or [[http://svkbook.elixus.org/|SVK book]]. | This is helpful if you wish to maintain a modified branch of Cytoscape under a local source control environment, and yet be able to synchronize with Cytoscape's svn (update / commit) from the same local directory. Find additional information in [[http://svk.bestpractical.com/view/HomePage|SVK home page]], [[http://svkbook.elixus.org/|SVK book]], and this [[http://jamiedubs.com/terse-subversion-tutorial|useful blog]], under "Using SVK for more decentralization". |
Cytoscape 3 Developer's Guide
Contents
- Cytoscape 3 Developer's Guide
- Cytoscape 3 Design Concept
- Working with Cytoscape 3 Code
- Related Frameworks and Technologies
Status
- This page should be updated for new tools. - Kei (8/16/2010)
- 1st draft of concept guide finished - Kei (3/10/2009)
- Concept guide section updated - Kei (3/11/2009)
Cytoscape 3 Design Concept
Introduction
A central goal of Cytoscape 3 is to make a more modularized, expandable, and maintainable version of Cytoscape. To achieve this goal, we follow several design principles and take advantage of three new technologies:
These technologies are very powerful, but you'll need to understand a few new concepts to use them easily and effectively. The purpose of this document is to introduce the new design concepts that you will need to develop Cytoscape 3 code.
Why Do We Have To Learn These New Technologies?
Cytoscape 2.x has several architectural problems. They are:
- There is no clear module structure and thus many circular dependencies.
- Unnecessary packages are open to public access. This results in broken plugins every time core developers update code we thought no one was using!
- Cytoscape 2.x has a package dependency graph that looks like this:
- View and model are tightly coupled in many places and make it hard to run in command line mode or as a server backend.
- There is no mechanism for allowing plugins to communicate with each other.
- Cytoscape has no mechanism for supporting different versions of libraries causing conflicts between plugins.
Central to each of these problems is a lack of a module system. Unfortunately, Java lacks an effective mechanism for defining modules. To address this need, we have decided to use OSGi as a mechanism for enforcing modularization. While OSGi provides many benefits, its most important is that it provides a new scoping mechanism that allows java packages to be declared public or private. This is accomplished by adding a few lines of metadata to the manifest file of a jar. Aside from the metadata the jars (which OSGi calls bundles) are normal java jar files.
OSGi developers have built upon this scoping mechanism and developed a framework (i.e. the Service Registry) as well as design patterns that allow developers to write very clean and simple code. Taking advantage of this framework, however, requires knowledge of and use of the OSGi API, which is not necessarily a simple thing. Dealing with this concern brings us to Spring.
Spring (specifically Spring Dynamic Modules) allows us to remove all OSGi dependencies from our code yet still take advantage of the Service Registry and other OSGi features. This means that you continue to write normal java POJOs. The cost is that your code is now initialized using Spring XML configuration code.
Finally, because OSGi breaks applications into many jars (called bundles) with dependencies and version numbers we have decided to use a build tool that has an explicit model for dealing with dependencies: Maven. You can think of Maven as a formalized version of Ant or even make. Unlike other tools, Maven provides a simple structure for declaring and using dependencies. We will take advantage of Maven's dependency handling to simplify the Cytoscape build process.
Important Design Principle of Cytoscape 3
Interface Oriented Design
Fundamental throughout Cytoscape 3 and OSGi is it's enforcement ofInterface Oriented Design. Rather than make both interfaces and implementations publicly available, Cytoscape 3 will only make interfaces public. All implementations of interfaces will be hidden in internal packages and will only be available through the OSGi Service Registry and Spring. The benefit of this approach is that you only need to concern yourself with an interface. You will never need to worry about specific implementation details. As long as you code to the interface, your code should continue to work.
From a plugin writer's perspective, you should:
- Hide unnecessary classes and only expose a limited number of interfaces. This makes your module (plugin) easy-to-use for other developers. OSGi provides the enforcement mechanisms for this.
- Separate implementation from API. This makes it easy to replace implementations without breaking other modules because they access your classes only through exported interfaces. Spring DM is one of the best tools for this job.
Dependency Injection
An important aspect of Cytoscape 3 development is the idea of Dependency Injection. In practical terms, this means that all dependencies for your classes should be specified in your constructor and you should no longer be creating (most) objects on your own.
For instance, if you need to create a new CyNetwork you will not call the constructor of a particular implementation of the CyNetwork interface. Instead you will have a CyNetworkFactory injected into your class (via its constructor) and then you will ask CyNetworkFactory to provide you with a new instance of CyNetwork. The benefit of this approach is that you never see the implementation of CyNetwork which frees us from a particularly onerous dependency - the ability to change implementation easily.
Micro Core Architecture
In Cytoscape 3, all modules are equal and there is no clear distinction between Core and Plugins. Instead of running Cytoscape on the top of Java Virtual Machine, we will insert one extra layer called the OSGi runtime. Cytoscape 3 is simply a set of bundles running on top of an OSGi micro core.
The diagram above shows overall architecture of Cytoscape 3. The most important point is that all of the inter-bundle communication is managed by the OSGi service registry, meaning through interfaces only, and not through concrete classes.
OSGi
OSGi is relatively complex technology, but the broad points that you should understand are as follows:
OSGi's unit of modularity is the bundle. A bundle is simply a jar file with extra metadata included in the manifest.
In Cytoscape 3, everything is a bundle. This means there is no longer a distinction between plugin and core.
- You must define which packages in your bundle are accessible to other bundles.
- You must define which classes are imported by your bundle. While seemingly cumbersome, this is generally automated and has the advantage of allowing you to specify specific version numbers of the library. OSGi allows different versions of the same library can exists in the same instance of a framework.
Any java interface can serve as the definition of an OSGi Service. Any implementation of an interface can thus be registered as an instance of that service in the OSGi Service Registry. All bundles can access those registered services.
These solves several problems we had in version 2.x:
Plugins can now communicate in a well defined way with other plugins. Plugins developers can publish small set of public interfaces for other developers while hiding your implementation classes from other developers EVEN IF THEY ARE PUBLIC CLASSES.
- Library version conflict. In Cytoscape 2, the entire application sometimes crashes due to library version conflicts. For example, if you use JAXB v2.0 in your plugin and other developer uses v2.1, the result is unpredictable. If we run Cytoscape on OSGi framework, this does not happen because you can specify which version of JAXB is required for your plugin and two different version of JAXB can run safely on the same JVM.
- Cytoscape developers can hide implementation details that plugin writers shouldn't have to worry about. This will allow us to change implementation details without fear of breaking plugins.
Suppose you want to develop a plugin to find clusters in a large network. You want to make your clustering algorithms accessible from other plugin developers. In that case, all you have to do is exporting the following interface:
1 public interface ClusteringService {
2 // Run my clustering algorithm for a given network "parent" and return clusters as a set of subnetworks.
3 public Set<CyNetwork> getClusters(CyNetwork parent);
4 }
5
If you export this as OSGi service, other developers can easily access your clustering algorithms programatically (or even from scripts because it's a part of Cytoscape 3 function). In addition, you can modify its implementation later without breaking other plugins because you are exposing interface (API) only and its implementation is completely hidden.
Spring Framework and Spring Dynamic Modules
Spring is the defacto standard of lightweight container for dependency injection. Tons of books and documents are available for Spring, and you can learn its mechanism by reading them. The role of Spring in Cytoscape 3 is simple: defining relationships between objects.
Wiring Objects
The diagram below shows the relationships between objects in Cytoscape 3 desktop application. In Spring, objects are defined as beans.
This diagram represents the cytoscapeDesktop bean, which is defined in a Bean Configuration XML file:
<bean name="cytoscapeDesktop" class="cytoscape.view.internal.CytoscapeDesktop"> <constructor-arg ref="cytoscapeMenus" /> <constructor-arg ref="networkViewManager" /> <constructor-arg ref="networkPanel" /> <constructor-arg ref="cytoscapeVersion" /> </bean>
Cytoscape Desktop is the main GUI component which includes some other components such as Network Panel or Menu Bar. To create an instance of Cytoscape Desktop, we need to provide those objects prepared somewhere outside of it. Instead of creating new instances of those required objects inside Cytoscape Desktop, you can inject them by Spring. In this case, all of the required dependency (cytoscapeMenus, networkViewManager, networkPanel, and cytoscapeVersion) are defined as beans in the Bean configuration file, and Spring injects those automatically to Cytoscape Desktop.
Separate Implementation from API
Spring is a powerful tool, but if we use Spring Dynamic Modules (Spring DM) along with it, we can take advantage of both Spring and OSGi.
Remember the ClusteringService in the last section. Without Spring, users of this object should do:
1 public class ClusteringServiceUser {
2 private ClusteringService service;
3
4 public ClusteringServiceUser() {
5 // Now ClusteringServiceUser depends on an implementation ClusteringServiceImpl.
6 service = new ClusteringServiceImpl();
7 }
8 }
9
This creates a dependency to specific implementation of ClusteringService API. With Spring, we can avoid this dependency:
1 AbstractApplicationContext ctx
2 = new ClassPathXmlApplicationContext(new String []{"bean-configuration.xml"});
3 service = ctx.getBean("clusteringServiceBean");
4
However, this introduces a new dependency to Spring Framework itself (AbstractApplicationContext). With Spring DM, the client code looks like the following:
1 public class ClusteringServiceUser {
2 private ClusteringService service;
3
4 public ClusteringServiceUser(ClusteringService service) {
5 // No dependency to frameworks!
6 this.service = service;
7 }
8
9 // Sample client code to find clusters for each network passes as parameter.
10 public Map<Integer, Set<CyNetwork>> analyzeNetwork(Set<CyNetwork> networks) {
11 final Map<Integer, Set<CyNetwork>> clusterMap = new HashMap<Integer, Set<CyNetwork>>();
12
13 for(CyNetwork net:networks) {
14 clusterMap.put(net.getSUDI(), service.getClusters(net));
15 }
16 return clusterMap;
17 }
18 }
19
In this case, the user object depends on ClusteringService interface only and completely independent from implementations or framework. But what's the magic behind this? The answer is the combination of service configuration file and Spring OSGi Extender bundle.
Service Provider
To create an instance of ClusteringServiceImpl, you can define it as a regular Spring bean:
<bean name="clusteringServiceBean" class="org.foo.clustering.iternal.ClusteringServiceImpl"> <constructor-arg ... </bean>
Now you need to export this as an OSGi service:
<osgi:service id="clusteringServiceBeanService" ref="clusteringServiceBean" interface="org.foo.clustering.ClusteringService" />
Spring OSGi Extender read the configuration file and export clusteringServiceBeanService as an OSGi service automatically.
Service Consumer
In the client (ClusteringServiceUser), you need to import clusteringServiceBeanService to inject the service to the client. This can be done by writing a simple enty in the XML file:
<osgi:reference id="clusteringServiceBeanServiceRef" interface="org.foo.clustering.ClusteringService" />
Once client import the service, you can use it as a regular Spring bean. Therefore, you can inject it like:
<bean name="clusteringServiceUserBean" class="org.bar.client.iternal.ClusteringServiceUserImpl"> <constructor-arg ref="clusteringServiceBeanServiceRef" /> </bean>
As you can see the example above, if we use Spring DM, Cytoscape 3 code is independent from OSGi API and Spring API. This means all of the Cytoscape objects are POJOs and we can replace the framework later when necessary.
Building System
This is not actually the architecture/design issue of Cytoscape 3, but is very important to understand to develop Cytoscape 3. In Cytoscape 2, we use build tool called Apache Ant. This is fairly flexible an powerful building tool, but have some issues:
Developers have to manage module dependency manually
- Cytoscape is a fairly complex application software using lots of public libraries (JARs). This means developers have to find correct version of libraries and copy them manually to the project directory. The bad news is most library JARs depends on other libraries and you need to figure out how they are related.
Need to repeat same thing again and again
Probably, the first thing developer do to begin Cytoscape 2 plugin development is copying build.xml file from somewhere (from your old project or other plugin code). This is because most developers follow very similar process, like compile, test, create jar, publish JavaDoc to web site, etc.
To solve these issues and time saving, we decide to use Apache Maven for official build tool for Cytoscape 3.
Convention over Configuration
The basic idea of Maven is Convention over Configuration. In most cases, Java developers follow very similar process: write code, compile, test, build JAR files, create JavaDoc, and publish it. In Maven, this model development style is defined as a convention and it is designed not to repeat the same configuration again and again. Instead of writing build.xml at the beginning of plugin development, you can start with type some maven command to create basic directory structure for your plugin. It looks like the following:
src test pom.xml
Because maven creates the basic directory structure, you do not have to configure details, such as location of source code, test cases, resource files, etc.
Also, Maven 2 has a very useful feature to import dependent libraries automatically over the internet. If you want to use library A, all you have to do is adding it to the pom.xml file. Then maven finds the dependent library for you.
Please try this example to understand basic concept of Maven2.
Working with Cytoscape 3 Code
Need to update the sections below.
Introduction
In this section, you are going to learn how to write Cytoscape 3 style code in the new building system.
Create OSGi-Spring Based Project
How to Use Logging for Debugging and Non-interruptive User Messages
Cytoscape provides a logging system for its bundles. Utilizing this logging system provides the following benefits:
Ability to issue non-interruptive user messages. During the execution of a piece of code, developers may need to communicate useful information to the user that does not require immediate attention. Messages must not be incomprehensible for a typical user. A recoverable parse error, which describes the nature of the problem and its location, is an example of a non-interruptive user message. The most recent non-interruptive user message is presented in Cytoscape's status bar. Users can review all user messages in the Console.
Developers can issue debug messages. They are only useful and informative for developers. These debug messages are then made available in the Console upon clicking the "Show all Messages" checkbox at the bottom of the Console. Developers can search through the log by entering a regular expression at the top of the Console and by choosing a specific log on the right of the Console.
Setting up Logging
Before using Cytoscape's logging system, include the following dependency in the bundle's pom.xml file:
<dependency> <groupId>org.ops4j.pax.logging</groupId> <artifactId>pax-logging-api</artifactId> <version>1.5.2</version> </dependency>
The above dependency provides access to the pax-logging-api package.
SLF4J
Cytoscape employs the SLF4J logging API. While the pax-logging-api package allows one to use other logging APIs, like Apache log4j or Tomcat Juli, it is highly discouraged to use any other API besides SLF4J.
At the beginning of a Java source file, include the following interfaces:
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
Obtain a Logger as follows:
Logger logger = LoggerFactory.getLogger(getClass());
Messages issued to the above logger will be sent to the logger with the fully qualified class path of the current class. For example, if the current class is "org.blah.foo.bar.internal.MyClass", messages are sent to the logger named "org.blah.foo.bar.internal.MyClass".
If one wishes to use a different logger, like "com.myproduct.some.path.AnotherClass", one can obtain that logger regardless of the class issuing the log messages:
Logger logger = LoggerFactory.getLogger("com.myproduct.some.path.AnotherClass");
After obtaining a logger, messages can then be issued to it:
logger.error("Whoa! Looks like I got an error");
Because one is issuing Strings as log messages, one can send a more detailed message:
logger.error("Whoa! Looks like I got some weird values. i = " + i + ", sum = " + sum);
The above code would construct the String even if the logger is set to discard error messages, thus incurring an unnecessary performance cost. SLF4J addresses this problem by using curly brackets:
logger.error("Whoa! Looks like I got some weird values. i = {}, sum = {}", i, sum);
If the above log message is discarded, there is no penalty incurred for the concatenated string.
Issuing Non-interruptive User Messages
Log messages that are only at the info or warn level are considered non-interruptive user messages.
logger.info("Hey there, Cytoscape user!"); logger.warn("The user will see this in the status bar.");
Issuing Debug Messages
Log messages issued at any level besides info and warn are considered debug messages.
logger.debug("This will not be seen by the user unless (s)he clicks \"Show all Messages\"");
How to Use Tasks
A task is a unit of work to be executed asynchronously. It is particularly useful for executing pieces of code that take a fair amount of time to complete and for allowing the programmer to communicate its status and progress to the user. Specifically, tasks are just like threads yet offer additional functionality:
- A task has a user interface that displays its state and progress.
- Users can request tasks to be cancelled. The task is then informed that the user has requested cancellation.
- If a fatal error occurs during the execution of a task, the user is appropriately informed, possibly with information to remedy the problem.
Setting Up
Before using tasks, it is necessary to specify a dependency to the Work API bundle. In the bundle's pom.xml file, one adds the following dependency:
<dependency> <groupId>org.cytoscape</groupId> <artifactId>work-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
A Preliminary Example
A simple yet computationally time-consuming task of calculating Pi to an arbitrary precision is presented here as an introduction. The following series is a means to calculate Pi:
Pi = 4 - 4/3 + 4/5 - 4/7 + 4/9 - 4/11 ...
Computation of the series can be formulated in Java as follows, where the variable iterations specifies how many iterations of the above series needs to be computed:
double sum = 4.0; boolean negative = true; for (long i = 1; i < iterations; i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } return sum;
Formulating a Task
The action or the algorithm of the task is wrapped in a class that implements the Task interface. The Task interface requires the following methods to be implemented:
public void run(TaskMonitor taskMonitor) throws Exception;
This method is called when the task begins execution. The programmer can communicate the status and progress of a task through the TaskMonitor object, which will be discussed later. The task can throw an exception when an unrecoverable error occurs.
public void cancel();
This method is called when the user has decided to cancel the task. Unlike the run method, cancel may not throw an uncaught exception.
The Pi calculating algorithm described above can be inserted into the run method as follows:
import org.cytoscape.work.Task; import org.cytoscape.work.TaskMonitor; public class PiCalculator implements Task { private final int iterations; public PiCalculator(int iterations) { this.iterations = iterations; } protected double sum = 4.0; public void run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; i < iterations; i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } } public void cancel() { } public double getSum() { return sum; } }
Notice throws Exception was not included above in the declaration of the run method. If the run method does not throw any uncaught exceptions, throws Exception is not necessary.
The task above, however, is quite impoverished. It ignores the user when (s)he wishes to cancel the task. If the user decides to cancel the task, it will continue to execute until run completes. Moreover, it doesn't inform the user about its progress or status. The following sections describe how to write a more well behaving, user friendly task.
Cancelling Tasks
The fundamental issue when considering cancellation is how the run() method is informed to prematurely finish its work. Writing a task that cancels cleanly requires some careful consideration, because the cancel method is executed in a thread separate from the one executing run. The Task API does not coerce tasks to finish. All properly written tasks ought to respond to cancellations quickly.
In the example above, one can add a boolean member to PiCalculator} that serves as a flag that is activated whenever the user decides to cancel the task. The run method can then check this member in its for loop, and if the member is flagged, it will terminate prematurely. This can be achieved as follows:
private boolean stop = false; public void run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } } public void cancel() { stop = true; }
Now when the user cancels the task,
the cancel method is invoked,
the stop member is flagged,
in its following iteration, the for loop in run checks the stop member,
and the for loop stops, ending the run method.
Using the TaskMonitor
Looking at the run method, tasks are provided a TaskMonitor. TaskMonitors allow the programmer to communicate the status and progress of a task to the user. TaskMonitor has the following methods:
setTitle - one provides a descriptive phrase specifying the overall goal of the task; this should be one of the first things called in the run method and should only be called once
setProgress - one gives a value from 0.0 to 1.0 to quantify the overall progress of the task; this should be called in main loops of a task
setStatusMessage - one specifies what the task is currently working on; this can be called repeatedly, unlike setTitle
The run method can be changed to use the TaskMonitor as follows:
public void run(TaskMonitor taskMonitor) { taskMonitor.setTitle("Pi Calculator"); boolean negative = true; taskMonitor.setStatusMessage("Calculating " + iterations + " iterations"); for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; taskMonitor.setProgress(i / ((double) iterations)); } }
Using TaskManagers
Now that writing the task is complete, how does one go about executing the task? It is done with a TaskManager, which will:
execute the task's run method in its own thread,
provide the run method with an instance of a TaskMonitor,
catch any exception thrown by run,
and call cancel when the user decides to cancel the task.
Before using the TaskManager, one must obtain an instance of a TaskManager by injecting it in the bundle-context-osgi.xml file:
<osgi:reference id="taskManagerRef" interface="org.cytoscape.work.TaskManager"/>
When one has an instance of a TaskManager, one can execute the PiCalculator task as follows:
PiCalculator piCalculator = new PiCalculator(...); taskManager.execute(piCalculator);
TaskManager's execute method will return once the task has started, not when it has completed.
Using ValuedTasks
The problem with the Task interface and TaskManagers is that there is no way to query the status of the task. They do not provide facilities to determine whether a task has finished or is still running. Moreover, tasks that produce some result have no means of returning it upon completion to the caller who started the task.
To address these limitations, the ValuedTask interface and the ValuedTaskExecutor class is provided in order to:
- determine whether the task is still running, has finished successfully, has been cancelled, or has encountered an error,
- and obtain the result of the task.
The ValuedTask interface is identical to the Task interface, except it allows the run method to return an object. The PiCalculator object can be reformulated according to a ValuedTask as follows:
import org.cytoscape.work.ValuedTask; ... public class PiCalculator implements ValuedTask<Double> { ... public Double run(TaskMonitor taskMonitor) { boolean negative = true; for (long i = 1; (i < iterations) && (!stop); i++) { sum += (negative ? -4.0 : 4.0) / (i * 2.0 + 1.0); negative = !negative; } return new Double(sum); } ... }
The problem with ValuedTask is that it cannot be executed by a TaskManager, which only accepts objects that implement Task. The ValuedTaskExecutor class addresses this problem by wrapping a ValuedTask object and is, in turn, acceptable to the TaskManager. Moreover, one can call methods in a ValuedTaskExecutor object to determine the status of the task. The PiCalculator class can be executed as follows:
PiCalculator piCalculator = new PiCalculator(...); ValuedTaskExecutor<Double> piCalculatorExecutor = new ValuedTaskExecutor<Double>(piCalculator); taskManager.execute(piCalculatorExecutor); System.out.println("PiCalculator's result is: " + piCalculatorExecutor.get());
Related Frameworks and Technologies
From 3.0, new technologies/frameworks will be used for implementation. The following is the list of links to documents related to those frameworks/technologies.
- OSGi - Cytoscape will be modularized by following this standard.
Apache Felix: Although OSGi is implementation-independent, this implementation will be used as standard development/testing tool.
OPS4J Products: This project has a lot of usuful scripts to develop OSGi bundles
SpringSource Bundle Repository: To use existing plain JAR libraries in an OSGi environment, we need to add metadata to it. This repository is a collection of OSGi-ed popular libraries, i.e., repository of OSGi bundles managed by Spring Source.
- Dependency Injection Frameworks - One of the scopes of 3.0 is modulalization and make Cytoscape usable as a part of other programs, including server-side application or command -line tools. The following DI frameworks are POJO-base and popular among server-side application developers.
Google Guice: Currently focused on DI only.
Spring Framework: De facto standard framework for DI and AOP.
Spring-DM: Integration of Spring Framework and OSGi
Apache Maven: 3.0 project will be moved from Ant to Maven.
Maven Repository Search - Note: Cytoscape cannot use most of these libraries directly. To use these, they should be re-packed as an OSGi bundle.
- Server-Side / SCA - Once Cytoscape is modulalized based on OSGi, we can use Cytoscape bundles and these frameworks to build server-side applications or web services.
Apache Tuscany: An open-source implementation of Service Component Architecture.
SpringSource Application Platform: OSGi-Spring powered application server.
- Scripting Language Support - There are several implementations of scripting languages running on JVM. These will be used to implement scripting feature in Cytoscape.
JRuby: Ruby scripting language running on JVM. Actively developed by Sun.
Rhino: JavaScript. Bundled with Java SE 6.
Jython: Python implementation.
Developer's Tutorials and References
For Eclipse Users
Basic Tutorial
Optional
Create Cytoscape 3 plugin from Maven Archetype (incomplete)
For Netbeans Users
Other OSGi Tools and Technical Notes
Books
Spring
Spring Recipes - Nice cookbook-style introduction to Spring framework and related projects. Based on 2.5.
Spring In Action - Based on version 2, but still good introduction to the framework.
Spring DM and OSGi
Pro Spring Dynamic Modules for OSGi™ Service Platforms - As far as I know, the only book which covers both Spring and OSGi.
Maven
Maven: The Definitive Guide - The best Maven 2 book on market, and online version is free!
Tricks
Ignoring .svn in Bash Auto-completion
Because of the restructuring of Cytoscape 3, the directory structures have become deeper than Cytoscape 2. Luckily, Bash makes it easy for us to cd quickly into a deep directory structure. When I type "cd src/main", then press tab repeatedly, I'll quickly find myself in src/main/java/org/cytoscape/.
Unfortunately this doesn't work when the directories are under version control, because Bash auto-completion finds .svn as well. Since I almost never need to enter the .svn directory, Bash auto-completion doesn't allow me to quickly traverse directory structures. I must read the name of the directory I want to enter, type its first letter, type tab again, and repeat until I'm in src/main/java/org/cytoscape. This may seem like a silly complaint about saving a few seconds, but considering that I find myself wanting to quickly access a source file repeatedly throughout the day, it serves as an effective distraction when I'm focused on getting the source file.
The way to get around this is to set the FIGNORE environment variable to ".svn". Bash reads this environment variable and filters out any auto-completion results that match FIGNORE. I also use FIGNORE to filter out .class files and .pyc files (compiled Python scripts), since I almost never need to open them. In my ~/.bashrc file, I have written:
export FIGNORE=".svn:.class:.pyc"
Searching for Classes in Javadocs
Javadocs displays classes in packages and the members of classes at a glance. However it doesn't provide any means to search for classes or members. As a substitute, I use Firefox's find command to look for a class. This is a poor substitute, because it searches all frames--meaning package names and class members as well. If I want to look up the javadocs for java.lang.String, I find myself having to hit the "Forward" button repeatedly until I find it.
I have found a script that installs a search box at the top of the list of classes/interfaces/exceptions in all javadoc pages. This means you type in the class/interface/exception name, and it'll give you a list that matches what you entered. If you press return when the search box has focus, it'll bring up the first class listed. It doesn't, unfortunately, search method names or class members.
You'll first need to install Greasemonkey (https://addons.mozilla.org/fr/firefox/addon/74), then install the Javadoc search script (http://code.google.com/p/javadoc-search-frame/). Afterwards, when you open a javadocs page, you'll see a search box above the list of classes. This is much better than using Firefox's find command.
Using SVK to mirror Cytoscape repositories
This is helpful if you wish to maintain a modified branch of Cytoscape under a local source control environment, and yet be able to synchronize with Cytoscape's svn (update / commit) from the same local directory. Find additional information in SVK home page, SVK book, and this useful blog, under "Using SVK for more decentralization".
Questions? Please e-mail me (kono_at_ucsd_edu)