Size: 49448
Comment: Added submenu placement advice (How to create a submenu?)
|
Size: 60152
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 7: | Line 7: |
= Examples = Look at the sample Apps [[https://github.com/cytoscape/cytoscape-samples/tree/master/|here]] (some of answers below might have no sample project yet, but most do). |
|
Line 11: | Line 16: |
In Cytoscape desktop, there are three CytoPanels, Control panel, data panel and result panel, located at West, south and east, respectively. New tabbed panel can be easily added to the CytoPanel. All the app developer should do is (1) defines a JPanel, which implements the CytoPanelComponnet; (2) register the new panel as OSGi service. The internal CytoPanel manager will automatically pick up the newly registered service identified as CytoPanelComponent and adds the new panel to the specified target CytoPanel. | In Cytoscape desktop, there are three !CytoPanels, Control panel, data panel and result panel, located at West, south and east, respectively. New tabbed panel can be easily added to the !CytoPanel. All the app developer should do is (1) defines a JPanel, which implements the !CytoPanelComponnet; (2) register the new panel as OSGi service. The internal !CytoPanel manager will automatically pick up the newly registered service identified as !CytoPanelComponent and adds the new panel to the specified target !CytoPanel. |
Line 38: | Line 43: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/AddCytoPanel|Here]]. |
|
Line 42: | Line 45: |
Sometimes, an app needs to add an menu item to the cytoscape menu or add an image icon on the Cytoscape toolbar. In such case, app developer needs to (1) define a class, which implements CyAction or extends AbstractCyAction; (2) and register the class as an OSGi service. The internal CyAction manger of Cytoscape will pick up the registered service and create the menu item as defined. Note the methods isInToolbar() and isInMenu(), its return value true or false will determine if menu item or image icon will be created or not. | Sometimes, an app needs to add an menu item to the cytoscape menu or add an image icon on the Cytoscape toolbar. In such case, app developer needs to (1) define a class, which implements !CyAction or extends !AbstractCyAction; (2) and register the class as an OSGi service. The internal !CyAction manger of Cytoscape will pick up the registered service and create the menu item as defined. Note the methods isInToolbar() and isInMenu(), its return value true or false will determine if menu item or image icon will be created or not. |
Line 74: | Line 77: |
Look at a sample app, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/AddImageIconOnToolBar|Here]]. |
|
Line 107: | Line 107: |
Look at a sample app, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/AddSubMenuItem|here]]. Note that apps can expose functionality using menus, and there is a spectrum of possibilities: * To expose a single function, an app can register a single submenu under the Apps menu (as shown above). * To expose multiple functions, an app can register a submenu under the Apps menu and then register individual functions as sub-submenus under the submenu. * Apps that have more complex interfaces can add submenus under non-App menus provided they supply documentation explaining the new submenus |
Note that apps can expose functionality using submenus, and there is a spectrum of possibilities: * To expose a single function, an app can register a single submenu (e.g., My !MenuItem) under the Apps menu (as shown above). * To expose multiple functions, an app can register a submenu under the Apps menu (e.g., My !AppMenu) and then register individual functions as sub-submenus (e.g., My !MenuItem1, My !MenuItem2, etc) under the submenu. * Apps that have more complex interfaces can add submenus under non-App menus provided they supply documentation explaining the new submenus -- this should be a rare occurrence. |
Line 166: | Line 164: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateDestroyNetwork|here]]. |
|
Line 170: | Line 166: |
The selection state of a node or edge is saved in the table associated with the network. Its attribute or column name is “selected”. Therefore, to determine the selection state of a node, we need to get the CyRow of the node and check the value of column “selected”. |
The selection state of a node or edge is saved in the table associated with the network. Its attribute or column name is “selected”. Therefore, to determine the selection state of a node, we need to get the !CyRow of the node and check the value of column “selected”. |
Line 180: | Line 177: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/GetSelectedNodes|Here]]. |
|
Line 183: | Line 178: |
The network name is kept in the table associated with the network, and its column name is “name”. To set the network name, first we need to get the CyRow of the network object, then set the “name” attribute of this row. |
The network name is kept in the table associated with the network, and its column name is “name”. To set the network name, first we need to get the !CyRow of the network object, then set the “name” attribute of this row. |
Line 192: | Line 188: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateDestroyNetwork|here]]. |
|
Line 197: | Line 190: |
The network title is kept in the table associated with the network, and its column name is “name”. To get the network name/title, first we need to get the CyRow of the network object, then get the “name” attribute of this row. | The network title is kept in the table associated with the network, and its column name is “name”. To get the network name/title, first we need to get the !CyRow of the network object, then get the “name” attribute of this row. |
Line 207: | Line 200: |
The name of a node is kept in the CyRow of the table associated with the network, and its column name is “name”. To set the node name, first we need to get the CyRow of the node object, then set the “name” attribute of this row. | The name of a node is kept in the !CyRow of the table associated with the network, and its column name is “name”. To set the node name, first we need to get the !CyRow of the node object, then set the “name” attribute of this row. |
Line 216: | Line 209: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateDestroyNetwork|here]]. |
|
Line 220: | Line 211: |
The name of a node is kept in the CyRow of the table associated with the network, and its column name is “name”. To get the node name, first we need to get the CyRow of the node object, then get the “name” attribute of this row. | The name of a node is kept in the !CyRow of the table associated with the network, and its column name is “name”. To get the node name, first we need to get the !CyRow of the node object, then get the “name” attribute of this row. |
Line 270: | Line 261: |
Look at a sample App, Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateNewTable|here]]. |
|
Line 274: | Line 262: |
'''Step 1''': get the CyTable through the network |
'''Step 1''': get the !CyTable through the network |
Line 288: | Line 277: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/DeleteAttribute|here]]. |
|
Line 375: | Line 362: |
== How to check if an attribute exists? == What if you're not interested in an attribute's current value but simply want to check if the column was defined of not? Use {{{CyTable.getColumn(String)}}} method: {{{#!java // CyTable cyTable = ... ... if(cyTable.getColumn("foo") != null) { // found ... } }}} E.g., to check for "foo" attribute in the default network table, use: {{{#!java ... boolean exists1 = cyNetwork.getDefaultNetworkTable() .getColumn("foo") != null; }}} instead of: {{{#!java ... boolean exists2 = cyNetwork.getRow(cyNetwork) .get("foo", fooType) != null; //wrong boolean exists3 = cyNetwork.getRow(cyNetwork) .isSet("foo"); //wrong // - both cannot tell non-existing from null-value attribute. }}} (Same for the default node and edge tables.) |
|
Line 448: | Line 476: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateDestroyNetworkview|here]]. |
|
Line 461: | Line 486: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/SetBackgroundColor|here]]. |
|
Line 464: | Line 487: |
Line 475: | Line 497: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/ZoomNetworkView|here]]. |
|
Line 496: | Line 515: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateNewLayout|here]]. |
|
Line 501: | Line 517: |
First define a layout class, which implements CyLayoutAlgorithm interface or extends AbstractLayoutAlgorithm class. Then register the layout class as a service. | First define a layout class, which implements !CyLayoutAlgorithm interface or extends !AbstractLayoutAlgorithm class. Then register the layout class as a service. |
Line 530: | Line 546: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateNewLayout|here]]. |
|
Line 534: | Line 547: |
To add a context menu to a NodeView, define a class, which extends AbstractNodeViewTaskFactory, and register the NodeViewTaskFactory as service. We can add the title of menu item by setting the service property when we register the nodeViewTaskFactory. To add context menu to the table browser, define a class, which extends AbstractTableCellTaskFactory. Create an instance and register it as service (Note register as TableCellTaskFactory). |
To add a context menu to a !NodeView, define a class, which extends !AbstractNodeViewTaskFactory, and register the !NodeViewTaskFactory as service. We can add the title of menu item by setting the service property when we register the nodeViewTaskFactory. To add context menu to the table browser, define a class, which extends !AbstractTableCellTaskFactory. Create an instance and register it as service (Note register as !TableCellTaskFactory). |
Line 553: | Line 567: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/AddNodeViewContextMenuItem|AddNodeViewContextMenuItem]]. Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateNetworkViewContextMenu|CreateNetworkViewContextMenu]]. |
Look at the sample Apps. == Code snippet for updating the network view == {{{ #!java public enum RedoLayout { YES, NO }; public enum RedoVisualStyle { YES, NO }; /** * Update a given CyNetworkView including optionally updating its layout and visual style. * @param view the CyNetworkView to layout * @param redoVS if redoVS=RedoVisualStyle.YES, apply the visual style associated with view. * @param redoLayout if redoLayout=RedoLayout.YES, apply the CyLayoutAlgorithm specified by layoutAlgorName * to view. If RedoLayout.NO, no layout is performed. * @param layoutAlgorName the name of the layout algorithm to apply. if null, the default * layout algorithm is used. If redoLayout=RedoLayout.NO * layoutAlgorName is ignored. * @throws IllegalArgumentException if layoutAlgorName is non-null, redoLayout=RedoLayout.YES, and no * layout algorithm is found. */ public static void updateView(CyNetworkView view, RedoVisualStyle redoVS, RedoLayout redoLayout, String layoutAlgorName) { if (redoLayout == RedoLayout.YES) { final CyLayoutAlgorithmManager alMan = _adapter.getCyLayoutAlgorithmManager(); CyLayoutAlgorithm algor = null; if (layoutAlgorName == null) { algor = alMan.getDefaultLayout(); } else { algor = alMan.getLayout(layoutAlgorName); } if (algor == null) { throw new IllegalArgumentException ("No such algorithm found '" + layoutAlgorName + "'."); } TaskIterator itr = algor.createTaskIterator(view, algor.createLayoutContext(), CyLayoutAlgorithm.ALL_NODE_VIEWS, null); _adapter.getTaskManager().execute(itr); // We use the synchronous task manager otherwise the visual style and updateView() // may occur before the view is relayed out: SynchronousTaskManager<?> synTaskMan = _adapter.getCyServiceRegistrar().getService(SynchronousTaskManager.class); synTaskMan.execute(itr); } if (redoVS == RedoVisualStyle.YES) { _adapter.getVisualMappingManager().getVisualStyle(view).apply(view); } view.updateView(); } }}} == How to set visual property values before node and edge views exist? == Cytoscape decouples the network topology and table models from its view model. The view model specifies the appearance or visualization of nodes and edges. When nodes and edges are created in a network, their view model objects are created only after a triggering of an event (`org.cytoscape.event.CyEventHelper.flushPayloadEvents()`). This is done to prevent Cytoscape from unnecessarily redrawing the network while a task is in the process of constructing a network. But the separation of the network and table models from the view model can be problematic if you need to assign visual property values to nodes and edges that don't have views at the time of their construction. To address this issue, we present the `DelayedVizProp` class: {{{ #!java import org.cytoscape.model.CyNode; import org.cytoscape.model.CyEdge; import org.cytoscape.model.CyIdentifiable; import org.cytoscape.view.model.CyNetworkView; import org.cytoscape.view.model.View; import org.cytoscape.view.model.VisualProperty; class DelayedVizProp { final CyIdentifiable netObj; final VisualProperty<?> prop; final Object value; final boolean isLocked; /** * Specify the desired visual property value for a node or edge. * @param netObj A CyNode or CyEdge * @param prop The visual property whose value you want to assign * @param value The visual property value you want to assign * @param isLocked true if you want the value to be set as a bypass value, false if you want the value to persist until a visual style is applied to the network view */ public DelayedVizProp(final CyIdentifiable netObj, final VisualProperty<?> prop, final Object value, final boolean isLocked) { this.netObj = netObj; this.prop = prop; this.value = value; this.isLocked = isLocked; } /** * Assign the visual properties stored in delayedProps to the given CyNetworkView. * @param netView The CyNetworkView that contains the nodes and edges for which you want to assign the visual properties * @param delayedProps A series of DelayedVizProps that specifies the visual property values of nodes and edges */ public static void applyAll(final CyNetworkView netView, final Iterable<DelayedVizProp> delayedProps) { for (final DelayedVizProp delayedProp : delayedProps) { final Object value = delayedProp.value; if (value == null) continue; View<?> view = null; if (delayedProp.netObj instanceof CyNode) { final CyNode node = (CyNode) delayedProp.netObj; view = netView.getNodeView(node); } else if (delayedProp.netObj instanceof CyEdge) { final CyEdge edge = (CyEdge) delayedProp.netObj; view = netView.getEdgeView(edge); } if (delayedProp.isLocked) { view.setLockedValue(delayedProp.prop, value); } else { view.setVisualProperty(delayedProp.prop, value); } } } } }}} While building your network, create instances of `DelayedVizProp` and store them in a `List`. After calling `org.cytoscape.event.CyEventHelper.flushPayloadEvents()`, call `applyAll` on the network view that contains your nodes and edges. |
Line 562: | Line 691: |
Cytosape provides services for using visual mapping programming. These services are VisualMappingManager, VisualStyleFactory and VisualMappingFunctionFactory. App should get references to these services in CyActivator class, the entry point of the app. We can create new visualStyle with VisualStyleFactory and create mapping function with VisualMappingFunctionFactory very easily. After a new visual style is created, it should register with the VisualMappingManger, in this way the new visual style will be available throughout Cytoscape. | Cytosape provides services for using visual mapping programming. These services are !VisualMappingManager, !VisualStyleFactory and !VisualMappingFunctionFactory. App should get references to these services in !CyActivator class, the entry point of the app. We can create new visualStyle with !VisualStyleFactory and create mapping function with !VisualMappingFunctionFactory very easily. After a new visual style is created, it should register with the !VisualMappingManger, in this way the new visual style will be available throughout Cytoscape. |
Line 596: | Line 725: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseVisualStyle|here]]. |
Look at the sample App. |
Line 600: | Line 728: |
Line 623: | Line 752: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/DegreeColorMapping|here]]. |
|
Line 627: | Line 753: |
Line 640: | Line 767: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/LoadVisualProperties|here]]. |
|
Line 661: | Line 785: |
Line 666: | Line 789: |
|
|
Line 678: | Line 799: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateNetworkReader_simpleApp|here]]. | Look at the sample App. |
Line 681: | Line 802: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateTableReader|here]]. |
|
Line 717: | Line 836: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseTaskMonitor|here]]. == How to issue non-blocking, informational messages to users? == Cytoscape 3.0 has a status bar at the bottom of the Cytoscape desktop window. Tasks can put messages into the status bar. These messages wouldn't require immediate user attention but are informational. Debug messages useful for developers do not belong in the status bar. To issue user messages to the status bar: '''Step 1'''. Include the SLF4J library as a dependency in your {{{pom.xml}}} file. {{{ #!xml <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> </dependency> }}} '''Step 2'''. Obtain an instance of the {{{CyUserMessages}}} log. {{{ #!java import org.slf4j.LoggerFactory; import org.slf4j.Logger; ... Logger userMessagesLogger = LoggerFactory.getLogger("CyUserMessages"); }}} '''Step 3'''. Issue log messages to the {{{CyUserMessages}}} log. {{{ #!java int lineError = 12; userMessagesLogger.warn("Parse error on line {}", lineError); }}} The message will appear in the status bar. |
|
Line 773: | Line 846: |
When the class is initialized, a core Cytoscape service, TunableInterceptor will inspect this class and generate UI based on the tunable annotation. In this case, a text field with attached description "scale", default value "0.2", will show up in the automatically generated dialog. Note that in above example, the data type is 'double', data type can also be 'int', 'boolean', 'String', or 'List'. If a field is defined as tunable, and its data type is 'boolean', then a radio button will be generated in the dialog. Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/ZoomNetworkView|here]]. |
When the class is initialized, a core Cytoscape service, !TunableInterceptor will inspect this class and generate UI based on the tunable annotation. In this case, a text field with attached description "scale", default value "0.2", will show up in the automatically generated dialog. Note that in above example, the data type is 'double', data type can also be 'int', 'boolean', 'String', or 'List'. If a field is defined as tunable, and its data type is 'boolean', then a radio button will be generated in the dialog. |
Line 781: | Line 851: |
There are two events, which are important for saving/restoring App state. The two evetns are SessionAboutToBeSavedEvent and SessionLoadedEvent. App should implement the two listeners and register them. | There are two events, which are important for saving/restoring App state. The two evetns are !SessionAboutToBeSavedEvent and !SessionLoadedEvent. App should implement the two listeners and register them. |
Line 813: | Line 883: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/SaveRestoreSession|here]]. |
|
Line 818: | Line 885: |
CyProperty acts as a container to save properties of the type (key, value) across sessions. They are available for the user to manually change in the preference menu option. | !CyProperty acts as a container to save properties of the type (key, value) across sessions. They are available for the user to manually change in the preference menu option. |
Line 873: | Line 940: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseCyProperty|here]]. |
|
Line 879: | Line 943: |
Line 899: | Line 964: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/HandleNetworkEvents|here]]. |
|
Line 904: | Line 967: |
Line 907: | Line 969: |
For example, the following command in CyActivator of the sample App will register a listener, which listens to the presence or absence of CyProperty service. | For example, the following command in !CyActivator of the sample App will register a listener, which listens to the presence or absence of !CyProperty service. |
Line 916: | Line 978: |
The two methods addPropertyService() and removePropertyService() should be defined in MyserviceListener. Note that the above example, will listen service event for CyProperty only, we can also listen to other service, such as TaskFactory, NetworkTaskFactory, CyLayoutAlgorithm, CytoPanelComponent, etc. Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseServiceListener|here]]. |
The two methods addPropertyService() and removePropertyService() should be defined in !MyserviceListener. Note that the above example, will listen service event for !CyProperty only, we can also listen to other service, such as !TaskFactory, !NetworkTaskFactory, !CyLayoutAlgorithm, !CytoPanelComponent, etc. |
Line 1010: | Line 1067: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseEquation|here]]. |
Look at the sample App. |
Line 1017: | Line 1073: |
First define a listener of web service client and register it as service -- WebServiceHelper. This class will keep tracker of web service clients available using a Map. | First define a listener of web service client and register it as service -- !WebServiceHelper. This class will keep tracker of web service clients available using a Map. |
Line 1055: | Line 1111: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/UseWebService|here]]. |
|
Line 1059: | Line 1112: |
Line 1069: | Line 1123: |
'''Step 2''': Define a client class, which must implement WebServiceClient and pass the UI class defined at previous step to the client. If UI is not defined, the default UI will be used. | '''Step 2''': Define a client class, which must implement !WebServiceClient and pass the UI class defined at previous step to the client. If UI is not defined, the default UI will be used. |
Line 1079: | Line 1133: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateWebServiceClient|here]]. |
== How to use Apache HTTP Client for working with web services == === TL;DR === Don't use `java.net.URLConnection`: it doesn't support cancellation and can be slow. Use [[http://hc.apache.org/httpcomponents-client-ga/|Apache's HTTP Client (HC)]] instead. === Introduction === Biomedical resources and databases are becoming increasingly accessible through public web services. Cytoscape apps that integrate these resources an HTTP client to access them. The Java standard library provides such an HTTP client: `HttpURLConnection` in the `java.net` package. However, `HttpURLConnection` has some significant limitations. Most importantly, it does not support cancellation. A well-written app correctly implements the `cancel()` method in the `Task` interface, which is vital for a responsive user interface. Users who are stuck behind an interrupted internet connection would need to back out of a HTTP request. [[http://hc.apache.org/httpcomponents-client-ga/|Apache's HTTP Client (HC)]] is an alternative third-party library to `HttpURLConnection` that addresses its limitations. Here's a comparison: '''Java standard library's HttpURLConnection''' Advantages: * Included with Java * Simple to use -- an entire HTTP client in a single class Disadvantages: * No support for cancellation -- impossible to cancel an HTTP request during task execution * No pooling of connections '''[[http://hc.apache.org/httpcomponents-client-ga/|Apache's HTTP Client (HC)]]''' Advantages: * HTTP requests can be canceled * HTTP connections are pooled for improved efficiency. A connection to a server is maintained for subsequent requests. * Supports multiple, simultaneous HTTP requests Disadvantages: * Requires your app to bundle the HC library * Complex -- it's an entire library just dedicated to HTTP client functionality * All HTTP requests must be properly closed, otherwise the connection pool becomes full and becomes impossible to issue subsequent requests HC's complexity can be daunting. To help with using HC, here we present a quick guide and some tips for working with HC 4.x. === Obtaining an HttpClient instance === {{{ #!java import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.CloseableHttpClient; ... final CloseableHttpClient client = HttpClientBuilder.create() // add more stuff here later to configure the client .build(); }}} Notes: * ''Do not'' create a client for each request. Use a single client instance for your all of your app's HTTP requests. `HttpClient` is powerful enough to handle multiple web services. * Call `CloseableHttpClient.close()` when your app is done making requests. === Creating and executing an HTTP request === With `HttpClient`, you can issue HTTP requests. An `HttpRequest` is an object that contains information about the request you want to make to the web service. At a minimum, it specifies a URL. It can also enclose data you want to send to the server, like form data or a JSON object. {{{ #!java import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpGet; ... final String url = ...; final HttpRequestBase req = new HttpGet(url); /* Or use HttpPost, HttpPut, etc. See the org.apache.http.client.methods package for other classes. */ final CloseableHttpResponse resp = client.execute(req); }}} Notes: * You ''must'' call both `CloseableHttpResponse.close()` and `HttpRequestBase.releaseConnection()`, even if an exception is thrown. It's best to put them in a `finally` block. Connections to the web service are pooled, and if the request and response objects are not closed, HC cannot use the connection again. * The `HttpClient.execute` method should be invoked inside a task. In your task's `cancel()` method, call `HttpRequestBase.abort()` to terminate the connection. ==== Posting a JSON object to the server ==== If you want to enclose a JSON object as part of your request to the server, you can use the [[http://jackson.codehaus.org/|Jackson]] library to construct the JSON object and send it to the server like so: {{{ #!java import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.InputStreamEntity; import org.apache.http.entity.ContentType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.TreeNode; import com.fasterxml.jackson.core.JsonEncoding; ... final String url = ...; final TreeNode jsonObject = ...; /* build the JSON object you want to send here */ // serialize the jsonObject to a byte array final ByteArrayOutputStream output = new ByteArrayOutputStream(); final ObjectMapper mapper = new ObjectMapper(); final JsonFactory jsonFactory = mapper.getFactory(); final JsonGenerator generator = jsonFactory.createGenerator(output, JsonEncoding.UTF8); generator.writeTree(jsonObject); output.flush(); output.close(); // we have a byte array of jsonObject, so now create an HttpEntity with our byte array final ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray()); final HttpPost post = new HttpPost(url); post.setEntity(new InputStreamEntity(input, ContentType.APPLICATION_JSON)); }}} === Ensuring that the server sent you a 2xx response === If you got a 2xx response status code, your request was successful. To check the response status code: {{{ #!java final int statusCode = resp.getStatusLine().getStatusCode(); if (200 <= statusCode && statusCode < 300) { // request succeeded! } else { // request failed :( } }}} === Reading the response === The response data from the server is stored in an ''`HttpEntity`'' object. The response data can be retrieved like so: {{{ #!java import org.apache.http.HttpEntity; ... final HttpEntity entity = resp.getEntity(); final String contentType = entity.getContentType() == null ? null : entity.getContentType().getValue(); final String charset = entity.getContentEncoding() == null ? null : entity.getContentEncoding().getValue(); final InputStream input = entity.getContent(); // read the input here }}} ==== Reading the response as a JSON object ==== Now that you have the response's encoding (`charset`) and a stream of bytes (`input`), you can read the response as a [[http://jackson.codehaus.org/|Jackson]] JSON object. {{{ #!java import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.JsonNode; ... final ObjectMapper mapper = new ObjectMapper(); final JsonNode responseAsJson = mapper.readValue(input, JsonNode.class); }}} ==== Reading the response as a String ==== This is complicated! You have to read the input to a byte array, then use that to construct a String. You can also use [[http://commons.apache.org/proper/commons-io/|Apache IO Commons library]]'s `IOUtils.toString` method. {{{ #!java final ByteArrayOutputStream output = new ByteArrayOutputStream(); final byte[] buffer = new byte[1024]; while (true) { final int len = input.read(buffer); if (len < 0) break; output.write(buffer, 0, len); } final String responseAsString = new String(output.toByteArray(), Charset.forName(charset == null ? "UTF-8" : charset)); }}} === Adding support for multiple, concurrent connections === {{{ #!java import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; ... final PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(); connMgr.setDefaultMaxPerRoute(16 /* this should match the number of concurrent connections you want to support */); final CloseableHttpClient client = HttpClientBuilder.create() .setConnectionManager(connMgr) .build(); }}} |
Line 1086: | Line 1328: |
If you download CreateHelp, compile and instal it through the App Manager of your running Cytoscape, you will see topic '''Sample 24 help''' is added to your help panel (Help --> Contents...). In order to make it simpler, you can download the [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateHelp/src/main/resources/help|help]] directory and copy this folder to your project's resources folder. Next you need to get the {{{CyHelpBroker}}} service in your CyActivator class. The code in CreateHelp: |
If you download !CreateHelp, compile and instal it through the App Manager of your running Cytoscape, you will see topic '''Sample 24 help''' is added to your help panel (Help --> Contents...). In order to make it simpler, you can download the [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateHelp/src/main/resources/help|help]] directory and copy this folder to your project's resources folder. Next you need to get the {{{CyHelpBroker}}} service in your !CyActivator class. The code in !CreateHelp: |
Line 1153: | Line 1395: |
Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/CreateHelp|here]]. |
|
Line 1224: | Line 1463: |
and add the dependency as usual |
(note: option "inline=true" in the Embed-Dependency sometimes causes bundle fail to load; so, try with and without it) - and add the dependency as usual |
Line 1265: | Line 1505: |
With this Embed-Dependency instruction in the configuration of maven-bundle-plugin, if your command 'mvn clean install' executes successful, you app jar will include the 3-party jars in itself. Look at a sample App, [[https://github.com/cytoscape/cytoscape-samples/tree/develop/EmbeddingDependencies|here]]. |
With this Embed-Dependency instruction in the configuration of maven-bundle-plugin, if your command 'mvn clean install' executes successful, you app jar will include the 3-party jars in itself. You can also there add {{{<Embed-Transitive>true</Embed-Transitive>}}} instruction, rather than guessing and adding each dependency manually, but this might bring unwanted and conflicting java libraries, which, however, can be fixed by also defining <exclusions> to some dependencies in your project. |
Do you want to share your own code snippet?
Let us know! Send a message to Cytoscape Discuss to have your snippet included in this cookbook.
Contents
- Do you want to share your own code snippet?
- Examples
- Swing Application
-
Model
- How to create, modify, and destroy a network, nodes, and edges?
- How to determine which nodes are currently selected on a network?
- How to set the name of a network?
- How to get the name of a network?
- How to set the name of a node?
- How to get the name of a node?
- How to load attribute data?
- How to remove attributes?
- How to get all the nodes with a specific attribute value?
- How to use model objects (CyNetwork, CyNode, etc.) when writing tests?
- How to check if an attribute exists?
-
View Model
- Getting node views for newly created nodes?
- How to create, modify, and destroy a network view?
- How to change the background color of a view?
- How to zoom a network view?
- How to get and set node coordinate positions?
- How to write a layout algorithm?
- How to add components to the node view, edge view, and attribute browser context menus?
- Code snippet for updating the network view
- How to set visual property values before node and edge views exist?
- VizMapper
- I/O
- Work
- Session
- Events
- Equations
- Web Services
- Help
- OSGi
Examples
Look at the sample Apps here (some of answers below might have no sample project yet, but most do).
Swing Application
How to add a tabbed Panel to Control panel?
In Cytoscape desktop, there are three CytoPanels, Control panel, data panel and result panel, located at West, south and east, respectively. New tabbed panel can be easily added to the CytoPanel. All the app developer should do is (1) defines a JPanel, which implements the CytoPanelComponnet; (2) register the new panel as OSGi service. The internal CytoPanel manager will automatically pick up the newly registered service identified as CytoPanelComponent and adds the new panel to the specified target CytoPanel.
Step 1
1 // Define a CytoPanel class
2 public class MyCytoPanel extends JPanel implements CytoPanelComponent {
3
4 ...
5 @Override
6 public CytoPanelName getCytoPanelName() {
7 return CytoPanelName.WEST;
8 }
9 ...
10 }
11
Step 2
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 MyCytoPanel myPanel = new MyPanel();
4 // Register it as a service:
5 registerService(bc,myCytoPanel,CytoPanelComponent.class, new Properties());
6
How to add an image icon (menu item) to the toolbar?
Sometimes, an app needs to add an menu item to the cytoscape menu or add an image icon on the Cytoscape toolbar. In such case, app developer needs to (1) define a class, which implements CyAction or extends AbstractCyAction; (2) and register the class as an OSGi service. The internal CyAction manger of Cytoscape will pick up the registered service and create the menu item as defined. Note the methods isInToolbar() and isInMenu(), its return value true or false will determine if menu item or image icon will be created or not.
Step 1
1 // Define a CyAction class
2 public class AddImageIconAction extends AbstractCyAction {
3
4 public AddImageIconAction(CySwingApplication desktopApp){
5 ...
6 ImageIcon icon = new ImageIcon(getClass().getResource("/images/tiger.jpg"));
7
8 putValue(LARGE_ICON_KEY, icon);
9 ...
10 }
11
12 public boolean isInToolBar() {
13 return true;
14 }
15 ...
16 }
17
Step 2
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 AddImageIconAction addImageIconAction = new AddImageIconAction(cytoscapeDesktopService);
4 // Register it as a service:
5 registerService(bc,addImageIconAction,CyAction.class, new Properties());
6
How to create a submenu?
The way to define a submenu item is similar to define a menu item. Besides the definition of menu item itself, it is also necessary to define its parent menu item, see below.
Step 1
1 // Define a CyAction class
2 public class MySubMenuItemAction extends AbstractCyAction {
3 ...
4 public MySubMenuItemAction(CySwingApplication desktopApp){
5 super("My Sub MenuItem...");
6 setPreferredMenu("Apps.My MenuItem");
7 //setMenuGravity(2.0f);
8 ...
9 }
10
Step 2
1 // In the start method of your CyActivator class:
2 // Create an instance:
3 MySubMenuItemAction action = new MySubMenuItemAction(cytoscapeDesktopService);
4
5 // Register it as a service:
6 registerService(bc,action,CyAction.class, new Properties());
7
Note that apps can expose functionality using submenus, and there is a spectrum of possibilities:
To expose a single function, an app can register a single submenu (e.g., My MenuItem) under the Apps menu (as shown above).
To expose multiple functions, an app can register a submenu under the Apps menu (e.g., My AppMenu) and then register individual functions as sub-submenus (e.g., My MenuItem1, My MenuItem2, etc) under the submenu.
- Apps that have more complex interfaces can add submenus under non-App menus provided they supply documentation explaining the new submenus -- this should be a rare occurrence.
Model
How to create, modify, and destroy a network, nodes, and edges?
To create a network, get a reference to the CyNetworkFactory service, and tell it to create a network. With the new network, nodes and edges can be created through the CyNetwork interface.
1 // To get a reference of CyNetworkFactory at CyActivator class of the App
2 CyNetworkFactory networkFactory = getService(bc, CyNetworkFactory.class);
3 ...
4
5 // Create a new network
6 CyNetwork myNet = networkFactory.createNetwork();
7
8 // Set name for network
9 myNet.getRow(net).set(CyNetwork.NAME, "My network");
10 ...
11
12 // Add two nodes to the network
13 CyNode node1 = myNet.addNode();
14 CyNode node2 = myNet.addNode();
15
16 // Set name for new nodes
17 myNet.getRow(node1).set(CyNetwork.NAME, "Node1");
18 myNet.getRow(node2).set(CyNetwork.NAME, "Node2");
19
20 // Add an edge
21 myNet.addEdge(node1, node2, true);
22
23 // Add the network to Cytoscape
24 CyNetworkManager networkManager = getService(bc, CyNetworkManager.class);
25 networkManager.addNetwork(myNet);
26
Destroying networks is done through the CyNetworkManager service.
First, in the start method of your CyActivator class:
1 // Get a CyNetworkManager
2 CyNetworkManager netMgr = getService(bc,CyNetworkManager.class);
3
Now you can use CyNetworkManager in your code:
1 // Destroy a network with NetworkManager
2 netMgr.destroyNetwork(myNet);
3
How to determine which nodes are currently selected on a network?
The selection state of a node or edge is saved in the table associated with the network. Its attribute or column name is “selected”. Therefore, to determine the selection state of a node, we need to get the CyRow of the node and check the value of column “selected”.
There is a util class CyTableUtil. We can use this class to get the list of selected nodes in a network
1 //Get the selected nodes
2 List<CyNode> nodes = CyTableUtil.getNodesInState(myNetwork,"selected",true);
3
How to set the name of a network?
The network name is kept in the table associated with the network, and its column name is “name”. To set the network name, first we need to get the CyRow of the network object, then set the “name” attribute of this row.
1 CyNetwork net = ...;
2 String name = ...;
3 net.getRow(net).set(CyNetwork.NAME, name);
4
How to get the name of a network?
The network title is kept in the table associated with the network, and its column name is “name”. To get the network name/title, first we need to get the CyRow of the network object, then get the “name” attribute of this row.
1 CyNetwork net = ...;
2 String name = net.getRow(net).get(CyNetwork.NAME, String.class);
3
How to set the name of a node?
The name of a node is kept in the CyRow of the table associated with the network, and its column name is “name”. To set the node name, first we need to get the CyRow of the node object, then set the “name” attribute of this row.
1 CyNode node = ...;
2 String myNodeName = ...;
3 net.getRow(node).set(CyNetwork.NAME, myNodeName);
4
How to get the name of a node?
The name of a node is kept in the CyRow of the table associated with the network, and its column name is “name”. To get the node name, first we need to get the CyRow of the node object, then get the “name” attribute of this row.
1 CyNode node = ...;
2 String myNodeName = net.getRow(node).get(CyNetwork.NAME, String.class);
3
How to load attribute data?
There are three steps to load attributes, (1) Create a global table with key "name"; (2) populate the newly created table with the attribute data; and (3) map the new table to the target table based on the key attribute.
After an attribute table is loaded, the attribute table will remain as an independent table inside Cytoscape, and its relationship to the merged table is maintained in the target table as a point or reference. The target table could be a table of node attribute, edge attribute or network table. When we look at the merged table, the newly created columns are called "virtual columns" of the merged table. In the table browser, virtual columns are colored differently from the other columns to indicated they are virtual columns.
1 // Define a task
2 public class CreateTableTask extends AbstractTask {
3 ....
4 @Override
5 public void run(TaskMonitor tm) throws IOException {
6 // Step 1: create a new table
7 CyTable table = tableFactory.createTable("MyAttrTable " + Integer.toString(numImports++),
8 "name", String.class, true, true);
9
10 // create a column for the table
11 String attributeName = "MyAttributeName";
12 table.createColumn(attributeName, Integer.class, false);
13
14 // Step 2: populate the table with some data
15 String[] keys = {"YLL021W","YBR170C","YLR249W"}; //map to the the "name" column
16 CyRow row = table.getRow(keys[0]);
17 row.set(attributeName, new Integer(2));
18
19 row = table.getRow(keys[1]);
20 row.set(attributeName, new Integer(3));
21
22 row = table.getRow(keys[2]);
23 row.set(attributeName, new Integer(4));
24
25 // We are loading node attribute
26 Class<? extends CyTableEntry> type = CyNode.class;
27
28 // Step 3: pass the new table to MapNetworkAttrTask
29 super.insertTasksAfterCurrentTask( new MapNetworkAttrTask(type,table,netMgr,appMgr,rootNetworkManager) );
30 }
31 ....
32 }
33
How to remove attributes?
Step 1: get the CyTable through the network
1 // case for Node table
2 CyTable nodeTable = network.getDefaultNodeTable();
3
Step 2: Find the column and delete it
1 if(nodeTable.getColumn(columnName)!= null){
2 nodeTable.deleteColumn(columnName);
3 }
4
How to get all the nodes with a specific attribute value?
Copy this method in your code:
1 /**
2 * Get all the nodes with a given attribute value.
3 *
4 * This method is effectively a wrapper around {@link CyTable#getMatchingRows}.
5 * It converts the table's primary keys (assuming they are node SUIDs) back to
6 * nodes in the network.
7 *
8 * Here is an example of using this method to find all nodes with a given name:
9 *
10 * {@code
11 * CyNetwork net = ...;
12 * String nodeNameToSearchFor = ...;
13 * Set<CyNode> nodes = getNodesWithValue(net, net.getDefaultNodeTable(), "name", nodeNameToSearchFor);
14 * // nodes now contains all CyNodes with the name specified by nodeNameToSearchFor
15 * }
16 * @param net The network that contains the nodes you are looking for.
17 * @param table The node table that has the attribute value you are looking for;
18 * the primary keys of this table <i>must</i> be SUIDs of nodes in {@code net}.
19 * @param colname The name of the column with the attribute value
20 * @param value The attribute value
21 * @return A set of {@code CyNode}s with a matching value, or an empty set if no nodes match.
22 */
23 private static Set<CyNode> getNodesWithValue(
24 final CyNetwork net, final CyTable table,
25 final String colname, final Object value)
26 {
27 final Collection<CyRow> matchingRows = table.getMatchingRows(colname, value);
28 final Set<CyNode> nodes = new HashSet<CyNode>();
29 final String primaryKeyColname = table.getPrimaryKey().getName();
30 for (final CyRow row : matchingRows)
31 {
32 final Long nodeId = row.get(primaryKeyColname, Long.class);
33 if (nodeId == null)
34 continue;
35 final CyNode node = net.getNode(nodeId);
36 if (node == null)
37 continue;
38 nodes.add(node);
39 }
40 return nodes;
41 }
42
How to use model objects (CyNetwork, CyNode, etc.) when writing tests?
Step 1. Include the JUnit framework as a dependency in your pom.xml:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>
Step 2. Include the model-impl package as a dependency in your pom.xml:
<dependency> <groupId>org.cytoscape</groupId> <artifactId>model-impl</artifactId> <version>${cytoscape.api.version}</version> <scope>test</scope> </dependency>
Step 3. In your testing code, use NetworkTestSupport to get a CyNetwork instance:
1 import org.cytoscape.model.NetworkTestSupport;
2
3 ...
4
5 final NetworkTestSupport nts = new NetworkTestSupport();
6 final CyNetwork network = nts.getNetwork();
7
8 // Now you have a CyNetwork!
9
How to check if an attribute exists?
What if you're not interested in an attribute's current value but simply want to check if the column was defined of not?
Use CyTable.getColumn(String) method:
1 // CyTable cyTable = ...
2 ...
3
4 if(cyTable.getColumn("foo") != null) {
5 // found
6 ...
7
8 }
9
E.g., to check for "foo" attribute in the default network table, use:
1 ...
2 boolean exists1 = cyNetwork.getDefaultNetworkTable()
3 .getColumn("foo") != null;
4
instead of:
1 ...
2
3 boolean exists2 = cyNetwork.getRow(cyNetwork)
4 .get("foo", fooType) != null; //wrong
5 boolean exists3 = cyNetwork.getRow(cyNetwork)
6 .isSet("foo"); //wrong
7 // - both cannot tell non-existing from null-value attribute.
8
(Same for the default node and edge tables.)
View Model
Getting node views for newly created nodes?
After creating a node and edge, this is how to get its view:
1 CyNetworkView networkView = ...;
2 CyNetwork network = networkView.getModel();
3 CyEventHelper eventHelper = ...;
4 CyNode newNode = network.addNode();
5 network.getRow(newNode).set(CyNetwork.NAME, "New Node");
6 eventHelper.flushPayloadEvents();
7 View<CyNode> newNodeView = networkView.getNodeView(newNode);
8
After creating the node in CyNetwork, you have to call CyEventHelper's flushPayloadEvents so that the new node gets a node view. If flushPayloadEvents is not called, getNodeView may return null.
If you are creating a bunch of nodes at once, call flushPayloadEvents after you have finished creating all the nodes, not after each node is created. Example:
1 CyNetworkView networkView = ...;
2 CyNetwork network = networkView.getModel();
3 CyEventHelper eventHelper = ...;
4
5 for (int i = 1; i <= 100; i++) {
6 CyNode node = network.addNode();
7 network.getRow(newNode).set(CyNetwork.NAME, "Node " + i);
8 }
9 eventHelper.flushPayloadEvents();
10
How to create, modify, and destroy a network view?
To create a network, get a reference to the CyNetworkViewFactory service, and tell it to create a network view.
First, in the start method of your CyActivator class:
1 // Get a CyNetworkViewFactory
2 CyNetworkViewFactory networkViewFactory = getService(bc, CyNetworkViewFactory.class);
3
Now you can use CyNetworkViewFactory in your code:
1 // Create a new network view
2 CyNetworkView myView = networkViewFactory.createNetworkView(myNet);
3
4 // Add view to Cytoscape
5 CyNetworkViewManager networkViewManager = getService(bc, CyNetworkViewManager.class);
6 networkViewManager.addNetworkView(myView);
7
Destroying network views is done through the CyNetworkViewManager service.
First, in the start method of your CyActivator class:
1 // get a CyNetworkViewManager
2 CyNetworkViewManager networkViewManager = getService(bc, CyNetworkViewManager.class);
3
Now you can use CyNetworkViewManager in your code:
1 // destroy a network view through NetworkViewManager
2 networkViewManager.destroyNetworkView(myView);
3
How to change the background color of a view?
Network background color is changed as a _visual property_.
1 // Set the background of current view to RED
2 view.setVisualProperty(BasicVisualLexicon.NETWORK_BACKGROUND_PAINT, Color.red);
3 view.updateView();
4
How to zoom a network view?
1 // Get the scale and adjust
2 double newScale = view.getVisualProperty(NETWORK_SCALE_FACTOR).doubleValue() * scale;
3 view.setVisualProperty(NETWORK_SCALE_FACTOR, newScale);
4 ...
5 view.updateView();
6
How to get and set node coordinate positions?
Given a View<CyNode>, here's how to get its x and y coordinate positions:
1 View<CyNode> nodeView = ...;
2 Double x = nodeView.getVisualProperty(BasicVisualLexicon.NODE_X_LOCATION);
3 Double y = nodeView.getVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION);
4
Here's how to set the x and y coordinate positions:
1 View<CyNode> nodeView = ...;
2 double x = ...;
3 double y = ...;
4 nodeView.setVisualProperty(BasicVisualLexicon.NODE_X_LOCATION, x);
5 nodeView.setVisualProperty(BasicVisualLexicon.NODE_Y_LOCATION, y);
6
How to write a layout algorithm?
First define a layout class, which implements CyLayoutAlgorithm interface or extends AbstractLayoutAlgorithm class. Then register the layout class as a service.
1 // Define a layout class
2 public class MyLayout extends AbstractLayoutAlgorithm {
3 ...
4 }
5
6
7 // Define a layout task class
8 public class MyLayoutTask extends AbstractLayoutTask {
9 ...
10
11 //Perform actual layout task
12 final protected void doLayout(final TaskMonitor taskMonitor) {
13 ...
14 }
15 ...
16 }
17
18
19 // Register the layout class as a service in CyActivator class
20 Properties myLayoutProps = new Properties();
21 myLayoutProps.setProperty("preferredMenu","My Layouts");
22 registerService(bc,myLayout,CyLayoutAlgorithm.class, myLayoutProps);
23
How to add components to the node view, edge view, and attribute browser context menus?
To add a context menu to a NodeView, define a class, which extends AbstractNodeViewTaskFactory, and register the NodeViewTaskFactory as service. We can add the title of menu item by setting the service property when we register the nodeViewTaskFactory.
To add context menu to the table browser, define a class, which extends AbstractTableCellTaskFactory. Create an instance and register it as service (Note register as TableCellTaskFactory).
1 // Define a class MyNodeViewTaskFactory
2 public class MyNodeViewTaskFactory extends AbstractNodeViewTaskFactory {
3 ...
4 }
5
6
7 // Register myNodeViewTaskFactory as a service in CyActivator
8 Properties myNodeViewTaskFactoryProps = new Properties();
9 myNodeViewTaskFactoryProps.setProperty("title","My context menu title");
10 registerService(bc,myNodeViewTaskFactory,NodeViewTaskFactory.class, myNodeViewTaskFactoryProps);
11
Look at the sample Apps.
Code snippet for updating the network view
1 public enum RedoLayout {
2 YES, NO
3 };
4 public enum RedoVisualStyle {
5 YES, NO
6 };
7
8 /**
9 * Update a given CyNetworkView including optionally updating its layout and visual style.
10 * @param view the CyNetworkView to layout
11 * @param redoVS if redoVS=RedoVisualStyle.YES, apply the visual style associated with view.
12 * @param redoLayout if redoLayout=RedoLayout.YES, apply the CyLayoutAlgorithm specified by layoutAlgorName
13 * to view. If RedoLayout.NO, no layout is performed.
14 * @param layoutAlgorName the name of the layout algorithm to apply. if null, the default
15 * layout algorithm is used. If redoLayout=RedoLayout.NO
16 * layoutAlgorName is ignored.
17 * @throws IllegalArgumentException if layoutAlgorName is non-null, redoLayout=RedoLayout.YES, and no
18 * layout algorithm is found.
19 */
20 public static void updateView(CyNetworkView view, RedoVisualStyle redoVS, RedoLayout redoLayout, String layoutAlgorName) {
21 if (redoLayout == RedoLayout.YES) {
22 final CyLayoutAlgorithmManager alMan = _adapter.getCyLayoutAlgorithmManager();
23 CyLayoutAlgorithm algor = null;
24 if (layoutAlgorName == null) {
25 algor = alMan.getDefaultLayout();
26 } else {
27 algor = alMan.getLayout(layoutAlgorName);
28 }
29 if (algor == null) {
30 throw new IllegalArgumentException ("No such algorithm found '" + layoutAlgorName + "'.");
31 }
32 TaskIterator itr = algor.createTaskIterator(view,
33 algor.createLayoutContext(),
34 CyLayoutAlgorithm.ALL_NODE_VIEWS,
35 null);
36 _adapter.getTaskManager().execute(itr);
37 // We use the synchronous task manager otherwise the visual style and updateView()
38 // may occur before the view is relayed out:
39 SynchronousTaskManager<?> synTaskMan = _adapter.getCyServiceRegistrar().getService(SynchronousTaskManager.class);
40 synTaskMan.execute(itr);
41 }
42 if (redoVS == RedoVisualStyle.YES) {
43 _adapter.getVisualMappingManager().getVisualStyle(view).apply(view);
44 }
45 view.updateView();
46 }
47
How to set visual property values before node and edge views exist?
Cytoscape decouples the network topology and table models from its view model. The view model specifies the appearance or visualization of nodes and edges. When nodes and edges are created in a network, their view model objects are created only after a triggering of an event (org.cytoscape.event.CyEventHelper.flushPayloadEvents()). This is done to prevent Cytoscape from unnecessarily redrawing the network while a task is in the process of constructing a network. But the separation of the network and table models from the view model can be problematic if you need to assign visual property values to nodes and edges that don't have views at the time of their construction. To address this issue, we present the DelayedVizProp class:
1 import org.cytoscape.model.CyNode;
2 import org.cytoscape.model.CyEdge;
3 import org.cytoscape.model.CyIdentifiable;
4
5 import org.cytoscape.view.model.CyNetworkView;
6 import org.cytoscape.view.model.View;
7 import org.cytoscape.view.model.VisualProperty;
8
9 class DelayedVizProp {
10 final CyIdentifiable netObj;
11 final VisualProperty<?> prop;
12 final Object value;
13 final boolean isLocked;
14
15 /**
16 * Specify the desired visual property value for a node or edge.
17 * @param netObj A CyNode or CyEdge
18 * @param prop The visual property whose value you want to assign
19 * @param value The visual property value you want to assign
20 * @param isLocked true if you want the value to be set as a bypass value, false if you want the value to persist until a visual style is applied to the network view
21 */
22 public DelayedVizProp(final CyIdentifiable netObj, final VisualProperty<?> prop, final Object value, final boolean isLocked) {
23 this.netObj = netObj;
24 this.prop = prop;
25 this.value = value;
26 this.isLocked = isLocked;
27 }
28
29 /**
30 * Assign the visual properties stored in delayedProps to the given CyNetworkView.
31 * @param netView The CyNetworkView that contains the nodes and edges for which you want to assign the visual properties
32 * @param delayedProps A series of DelayedVizProps that specifies the visual property values of nodes and edges
33 */
34 public static void applyAll(final CyNetworkView netView, final Iterable<DelayedVizProp> delayedProps) {
35 for (final DelayedVizProp delayedProp : delayedProps) {
36 final Object value = delayedProp.value;
37 if (value == null)
38 continue;
39
40 View<?> view = null;
41 if (delayedProp.netObj instanceof CyNode) {
42 final CyNode node = (CyNode) delayedProp.netObj;
43 view = netView.getNodeView(node);
44 } else if (delayedProp.netObj instanceof CyEdge) {
45 final CyEdge edge = (CyEdge) delayedProp.netObj;
46 view = netView.getEdgeView(edge);
47 }
48
49 if (delayedProp.isLocked) {
50 view.setLockedValue(delayedProp.prop, value);
51 } else {
52 view.setVisualProperty(delayedProp.prop, value);
53 }
54 }
55 }
56 }
57
While building your network, create instances of DelayedVizProp and store them in a List. After calling org.cytoscape.event.CyEventHelper.flushPayloadEvents(), call applyAll on the network view that contains your nodes and edges.
VizMapper
How to use the VizMapper programmatically?
Cytosape provides services for using visual mapping programming. These services are VisualMappingManager, VisualStyleFactory and VisualMappingFunctionFactory. App should get references to these services in CyActivator class, the entry point of the app. We can create new visualStyle with VisualStyleFactory and create mapping function with VisualMappingFunctionFactory very easily. After a new visual style is created, it should register with the VisualMappingManger, in this way the new visual style will be available throughout Cytoscape.
1 // To get references to services in CyActivator class
2 VisualMappingManager vmmServiceRef = getService(bc,VisualMappingManager.class);
3
4 VisualStyleFactory visualStyleFactoryServiceRef = getService(bc,VisualStyleFactory.class);
5
6 VisualMappingFunctionFactory vmfFactoryC = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=continuous)");
7 VisualMappingFunctionFactory vmfFactoryD = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=discrete)");
8 VisualMappingFunctionFactory vmfFactoryP = getService(bc,VisualMappingFunctionFactory.class, "(mapping.type=passthrough)");
9
10
11 // To create a new VisualStyle object and set the mapping function
12 VisualStyle vs= this.visualStyleFactoryServiceRef.createVisualStyle("My visual style");
13
14
15 //Use pass-through mapping
16 String ctrAttrName1 = "SUID";
17 PassthroughMapping pMapping = (PassthroughMapping) vmfFactoryP.createVisualMappingFunction(ctrAttrName1, String.class, attrForTest, BasicVisualLexicon.NODE_LABEL);
18
19 vs.addVisualMappingFunction(pMapping);
20
21
22 // Add the new style to the VisualMappingManager
23 vmmServiceRef.addVisualStyle(vs);
24
25
26 // Apply the visual style to a NetwokView
27 vs.apply(myNetworkView);
28 myNetworkView.updateView();
29
Look at the sample App.
How to apply a continuous color gradient to nodes according to their degree?
1
2 // Set node color map to attribute "Degree"
3 ContinuousMapping mapping = (ContinuousMapping)
4 this.continuousMappingFactoryServiceRef.createVisualMappingFunction("Degree", Integer.class, BasicVisualLexicon.NODE_FILL_COLOR);
5
6 // Define the points
7 Double val1 = 2d;
8 BoundaryRangeValues<Paint> brv1 = new BoundaryRangeValues<Paint>(Color.RED, Color.GREEN, Color.PINK);
9
10 Double val2 = 12d;
11 BoundaryRangeValues<Paint> brv2 = new BoundaryRangeValues<Paint>(Color.WHITE, Color.YELLOW, Color.BLACK);
12
13 // Set the points
14 mapping.addPoint(val1, brv1);
15 mapping.addPoint(val2, brv2);
16
17 // add the mapping to visual style
18 vs.addVisualMappingFunction(mapping);
19
How to load a visual properties file?
Cytoscape provide a service 'LoadVizmapFileTaskFactory' for loading visual styles definded in a property file.
1 // get a reference to Cytoscape service -- LoadVizmapFileTaskFactory
2 LoadVizmapFileTaskFactory loadVizmapFileTaskFactory = getService(bc,LoadVizmapFileTaskFactory.class);
3
1 // Use the service to load visual style, 'f' is the File object to hold the visual properties
2 Set<VisualStyle> vsSet = loadVizmapFileTaskFactory.loadStyles(f);
3
I/O
How to build a network reader to support my own format?
First, we should define the format of my network file and its file extension. Let's say, each line in our network file has two columns, tab-delimited. And we define file extension '.tc', stands for 'two columns'.
1 //1. define a file filter (BasicCyFileFilter), to support the reader to read the file with extension '.tc'
2 HashSet<String> extensions = new HashSet<String>();
3 extensions.add("tc");
4 HashSet<String> contentTypes = new HashSet<String>();
5 contentTypes.add("txt");
6 String description = "My test filter";
7 DataCategory category = DataCategory.NETWORK;
8 BasicCyFileFilter filter = new BasicCyFileFilter(extensions,contentTypes, description, category, swingAdapter.getStreamUtil());
9
10 //2. Create an instance of the ReaderFactory
11 // Note that extends TCReaderFactory must implement the interface InputStreamTaskFactory or extends the class AbstractInputStreamTaskFactory.
12 // And the defined task must implement CyNetworkReader
13 TCReaderFactory factory = new TCReaderFactory(filter, swingAdapter.getCyNetworkFactory(), swingAdapter.getCyNetworkViewFactory());
14
15 //3. register the ReaderFactory as an InputStreamTaskFactory.
16 Properties props = new Properties();
17 props.setProperty("readerDescription","TC file reader");
18 props.setProperty("readerId","tcNetworkReader");
19 swingAdapter.getCyServiceRegistrar().registerService(factory, InputStreamTaskFactory.class, props);
20
Compile the following sample App, and install the app in Cytoscape. When we try to import a network from a file (File-->Import-->Network-->File..), we will find file type '.tc' is listed as one of the file types supported by Cytoscape.
Look at the sample App.
How to build a table reader to support my own format?
Work
How to use the Cytoscape task monitor to show the progress of my job?
Get a Cytoscape service DialogTaskManager and execute the task through the taskManager.
1 // Get a Cytoscape service 'DialogTaskManager' in CyActivator class
2 DialogTaskManager dialogTaskManager = getService(bc, DialogTaskManager.class);
3
4
5 // Define a task and set the progress in the run() method
6 public class MyTask extends AbstractTask {
7 ...
8 public void run(final TaskMonitor taskMonitor) {
9 // Give the task a title.
10 taskMonitor.setTitle("My task");
11 ...
12 taskMonitor.setProgress(0.1);
13
14 // do something here
15
16 ...
17 taskMonitor.setProgress(1.0);
18 }
19
20
21 // Execute the task through the TaskManager
22 DialogTaskManager.execute(myTaskFactory);
23
How to use Tunable?
An app might need to generate dialog and get input from user. An easy way to generate such dialog is to define a Task and use tunable annotation, and let Cytoscape generate the dialog automatically. In the Task class, define a class field, its data type, and description as the following example.
1 @Tunable(description="Scale")
2 public double scale = 0.2; // Default value
3
When the class is initialized, a core Cytoscape service, TunableInterceptor will inspect this class and generate UI based on the tunable annotation. In this case, a text field with attached description "scale", default value "0.2", will show up in the automatically generated dialog. Note that in above example, the data type is 'double', data type can also be 'int', 'boolean', 'String', or 'List'. If a field is defined as tunable, and its data type is 'boolean', then a radio button will be generated in the dialog.
Session
How to save/restore app states?
There are two events, which are important for saving/restoring App state. The two evetns are SessionAboutToBeSavedEvent and SessionLoadedEvent. App should implement the two listeners and register them.
1 // Implements the session event listeners
2 public class MyClass implements SessionAboutToBeSavedListener, SessionLoadedListener {
3
4 // Save app state in a file
5 public void handleEvent(SessionAboutToBeSavedEvent e){
6 // save app state file "myAppStateFile"
7 ...
8 }
9
10 // restore app state from a file
11 public void handleEvent(SessionLoadedEvent e){
12
13 if (e.getLoadedSession().getAppFileListMap() == null || e.getLoadedSession().getAppFileListMap().size() ==0){
14 return;
15 }
16 List<File> files = e.getLoadedSession().getAppFileListMap().get("myAppStateFile");
17 ...
18 }
19 }
20
21
22 // Register the two listeners in the CyActivator class
23 registerService(bc,myClass,SessionAboutToBeSavedListener.class, new Properties());
24 registerService(bc,myClass,SessionLoadedListener.class, new Properties());
25
How to add CyProperty to save values across sessions?
CyProperty acts as a container to save properties of the type (key, value) across sessions. They are available for the user to manually change in the preference menu option.
1 //1. Adding a property which saves the node border width selected by the user.
2 public static String NodeBorderWidthInPaths = "NODE_BORDER_WIDTH_IN_PATHS";
3 public static Double NodeBorderWidthInPathsValue = 20.0;
4 public static Properties nodeBorderWidthProps = new Properties();
5
6 //2. Find if the CyProperty already exists, if not create one with default value.
7 CyProperty<Properties> nodeBorderWidthProperty = null;
8 CySessionManager mySessionManager;
9 mySessionManager = adapter.getCySessionManager();
10 CySession session;
11 session = mySessionManager.getCurrentSession();
12 if(session.equals(null))
13 System.out.println("session null");
14
15 //3. Get all properties and loop through to find your own.
16 Set<CyProperty<?>> props = new HashSet<CyProperty<?>>();
17 props = session.getProperties();
18 if(props.equals(null))
19 System.out.println("props null");
20 boolean flag = false;
21
22 for (CyProperty<?> prop : props) {
23 if (prop.getName() != null){
24 if (prop.getName().equals(NodeBorderWidthInPaths)) {
25 nodeBorderWidthProperty = (CyProperty<Properties>) prop;
26 flag = true;
27 break;
28 }
29 }
30 }
31
32 //4. If the property does not exists, create nodeBorderWidthProperty
33 if (!flag)
34 {
35 nodeBorderWidthProps.setProperty(NodeBorderWidthInPaths, NodeBorderWidthInPathsValue.toString());
36 nodeBorderWidthProperty = new
37 SimpleCyProperty(NodeBorderWidthInPaths,
38 nodeBorderWidthProps, Float.TYPE, CyProperty.SavePolicy.SESSION_FILE_AND_CONFIG_DIR );
39 }
40 //5. If not null, property exists, get value from it and set NodeBorderWidthInPathsValue
41 else
42 {
43 nodeBorderWidthProps = nodeBorderWidthProperty.getProperties();
44 NodeBorderWidthInPathsValue = Double.valueOf((String)nodeBorderWidthProps.get(NodeBorderWidthInPaths));
45 }
46
47 //6. Register the CyProperty nodeBorderWidthProperty as OSGi service
48 registerService(bc,nodeBorderWidthProperty,CyProperty.class, new Properties());
49
Events
How to handle events from a network
Step 1
1 // Define a class, which implements a listener interface
2 public class MyListenerClass implements NetworkAddedListener {
3 ....
4 public void handleEvent(NetworkAddedEvent e){
5 // do something here
6 }
7 }
8
Step 2
1 // In the `start` method of your `CyActivator` class,
2 // register the listener in the CyActivator class
3 registerService(bc,myListenerClass, NetworkAddedListener.class, new Properties());
4
How to use Service listener
Cytoscape is an OSGi based application, service can be registered and unregistered in the OSGi container. An app can listen such event to react based on the service added or removed from the OSGi container.
For example, the following command in CyActivator of the sample App will register a listener, which listens to the presence or absence of CyProperty service.
1 registerServiceListener(bc, myserviceListener, "addPropertyService", "removePropertyService", CyProperty.class);
2
The two methods addPropertyService() and removePropertyService() should be defined in MyserviceListener. Note that the above example, will listen service event for CyProperty only, we can also listen to other service, such as TaskFactory, NetworkTaskFactory, CyLayoutAlgorithm, CytoPanelComponent, etc.
Equations
How to add new attribute functions via a Cytoscape App?
Here we will go through all the steps necessary to create a new built-in function IXOR(). The complete example code and can be downloaded from here. The easiest part is writing the actual plug-in class, which look similar to this:
1 import org.cytoscape.equations.EquationCompiler;
2 import org.cytoscape.equations.Interpreter;
3 import org.cytoscape.equations.EquationParser;
4
5 public class Sample23 {
6
7 public Sample23(EquationCompiler eqCompilerRef, Interpreter interpreterRef){
8 final EquationParser theParser = eqCompilerRef.getParser();
9 theParser.registerFunction(new IXor());
10 }
11 }
12
Here we register a new built-in function called IXor. You can also register multiple built-in functions if you prefer.
The next part is creating one class each for every new built-in. Each such class has to implement the org.cytoscape.equations.Function interface usually via org.cytoscape.equations.AbstractFunction. The easiest way to get started is to peruse the existing built-ins in equations Cytoscape core library and look for a function with a similar or identical argument list. If you can't find one it may still be instructive to read through the implementation of a couple of existing functions.
First you have to create the constructor where you describe the arguments that your function will take:
1 public IXor() {
2 super(new ArgDescriptor[] {
3 new ArgDescriptor(ArgType.INT, "arg1", "A quantity that can be converted to an integer."),
4 new ArgDescriptor(ArgType.INT, "arg2", "A quantity that can be converted to an integer."),
5 });
6
The most trivial to implement methods are getName(), and getFunctionSummary().
1 /**
2 * Used to parse the function string. This name is treated in a case-insensitive manner!
3 * @return the name by which you must call the function when used in an attribute equation.
4 */
5 public String getName() { return "IXOR"; }
6
7 /**
8 * Used to provide help for users.
9 * @return a description of what this function does
10 */
11 public String getFunctionSummary() { return "Returns an integer value that is the exclusive-or of 2 other integer values."; }
12
The next method is still simple but already touches on the one area that is somewhat complicated in attribute functions: data types and data type conversions!
1 public Class getReturnType() { return Long.class; }
2
Our example function is supposed to return an integer value. Integers in equation functions are represented as instances of class java.lang.Long. Therefore it is imperative not to return Integer.class! The user of the function can be blissfully unaware of this distinction. Just remember to always substitute Long for Integer and you should be fine.
The next method evaluateFunction() is the one that gets called when an expression is being evaluated. No argument type-checking is needed here because the compiler already took care of that for us. But, we need to handle all possible valid argument types and counts. (N.B., functions may be overloaded and/or variadic.) In this example the arguments can be any combination of Long and Double.
1 public Object evaluateFunction(final Object[] args) {
2 long arg1;
3 try {
4 arg1 = FunctionUtil.getArgAsLong(args[0]);
5 } catch (final Exception e) {
6 throw new IllegalArgumentException("IXOR: can't convert the 1st argument to an integer!");
7 }
8
9 long arg2;
10 try {
11 arg2 = FunctionUtil.getArgAsLong(args[0]);
12 } catch (final Exception e) {
13 throw new IllegalArgumentException("IXOR: can't convert the 2nd argument to an integer!");
14 }
15
16 final long result = arg1 ^ arg2;
17 return (Long)result;
18 }
19
This tutorial on how to write attribute functions may also be helpful.
Look at the sample App.
Web Services
How to use a web service client?
First define a listener of web service client and register it as service -- WebServiceHelper. This class will keep tracker of web service clients available using a Map.
1 public class WebServiceHelper {
2 ...
3 public void addWebServiceClient(final WebServiceClient newFactory,
4 final Map properties)
5 {
6 webserviceMap.put(newFactory, properties);
7 }
8
9 public void removeWebServiceClient(final WebServiceClient factory,
10 final Map properties)
11 {
12 webserviceMap.remove(factory);
13 }
14 ...
15 }
16
At running time, check if the needed service is available by looking at the map.
1 Iterator<WebServiceClient> it = webserviceMap.keySet().iterator();
2
3 while (it.hasNext()){
4 WebServiceClient client = it.next();
5 // Based on the serviceLocation, we can find if the client we are looking for is available
6 System.out.println("WebService CLient client: DisplayName() is "+client.getDisplayName());
7 System.out.println("WebService CLient client: ServiceLocation() is "+client.getServiceLocation());
8 ....
9 }
10
If the needed client is found, then we can construct the query and execute the query through the API of the client.
How to write a web service client?
Step 1: Define a UI class for the setting of the client. This UI may listen to Cytoscape events
1 public class MyWebserviceClientPanel extends JPanel implements ColumnCreatedListener, ColumnDeletedListener,
2 SetCurrentNetworkListener {
3 ...
4 }
5
Step 2: Define a client class, which must implement WebServiceClient and pass the UI class defined at previous step to the client. If UI is not defined, the default UI will be used.
1 public class MyWebserviceClient extends AbstractWebServiceGUIClient implements TableImportWebServiceClient {
2 ...
3 }
4
Step 3: After the client is registered as service, the client can be find under File-->Import->Table-->Public databases...
How to use Apache HTTP Client for working with web services
TL;DR
Don't use java.net.URLConnection: it doesn't support cancellation and can be slow. Use Apache's HTTP Client (HC) instead.
Introduction
Biomedical resources and databases are becoming increasingly accessible through public web services. Cytoscape apps that integrate these resources an HTTP client to access them. The Java standard library provides such an HTTP client: HttpURLConnection in the java.net package. However, HttpURLConnection has some significant limitations. Most importantly, it does not support cancellation. A well-written app correctly implements the cancel() method in the Task interface, which is vital for a responsive user interface. Users who are stuck behind an interrupted internet connection would need to back out of a HTTP request. Apache's HTTP Client (HC) is an alternative third-party library to HttpURLConnection that addresses its limitations. Here's a comparison:
Java standard library's HttpURLConnection
Advantages:
- Included with Java
- Simple to use -- an entire HTTP client in a single class
Disadvantages:
- No support for cancellation -- impossible to cancel an HTTP request during task execution
- No pooling of connections
Advantages:
- HTTP requests can be canceled
- HTTP connections are pooled for improved efficiency. A connection to a server is maintained for subsequent requests.
- Supports multiple, simultaneous HTTP requests
Disadvantages:
- Requires your app to bundle the HC library
- Complex -- it's an entire library just dedicated to HTTP client functionality
- All HTTP requests must be properly closed, otherwise the connection pool becomes full and becomes impossible to issue subsequent requests
HC's complexity can be daunting. To help with using HC, here we present a quick guide and some tips for working with HC 4.x.
Obtaining an HttpClient instance
1 import org.apache.http.impl.client.HttpClientBuilder;
2 import org.apache.http.impl.client.CloseableHttpClient;
3
4 ...
5
6 final CloseableHttpClient client =
7 HttpClientBuilder.create()
8 // add more stuff here later to configure the client
9 .build();
10
Notes:
Do not create a client for each request. Use a single client instance for your all of your app's HTTP requests. HttpClient is powerful enough to handle multiple web services.
Call CloseableHttpClient.close() when your app is done making requests.
Creating and executing an HTTP request
With HttpClient, you can issue HTTP requests. An HttpRequest is an object that contains information about the request you want to make to the web service. At a minimum, it specifies a URL. It can also enclose data you want to send to the server, like form data or a JSON object.
1 import org.apache.http.client.methods.HttpRequestBase;
2 import org.apache.http.client.methods.HttpGet;
3
4 ...
5
6 final String url = ...;
7 final HttpRequestBase req = new HttpGet(url); /* Or use HttpPost, HttpPut, etc. See the org.apache.http.client.methods package for other classes. */
8 final CloseableHttpResponse resp = client.execute(req);
9
Notes:
You must call both CloseableHttpResponse.close() and HttpRequestBase.releaseConnection(), even if an exception is thrown. It's best to put them in a finally block. Connections to the web service are pooled, and if the request and response objects are not closed, HC cannot use the connection again.
The HttpClient.execute method should be invoked inside a task. In your task's cancel() method, call HttpRequestBase.abort() to terminate the connection.
Posting a JSON object to the server
If you want to enclose a JSON object as part of your request to the server, you can use the Jackson library to construct the JSON object and send it to the server like so:
1 import java.io.ByteArrayOutputStream;
2 import java.io.ByteArrayInputStream;
3
4 import org.apache.http.client.methods.HttpPost;
5 import org.apache.http.entity.InputStreamEntity;
6 import org.apache.http.entity.ContentType;
7
8 import com.fasterxml.jackson.databind.ObjectMapper;
9 import com.fasterxml.jackson.core.JsonFactory;
10 import com.fasterxml.jackson.core.JsonGenerator;
11 import com.fasterxml.jackson.core.JsonEncoding;
12 import com.fasterxml.jackson.core.TreeNode;
13 import com.fasterxml.jackson.core.JsonEncoding;
14
15 ...
16
17 final String url = ...;
18 final TreeNode jsonObject = ...; /* build the JSON object you want to send here */
19
20 // serialize the jsonObject to a byte array
21 final ByteArrayOutputStream output = new ByteArrayOutputStream();
22 final ObjectMapper mapper = new ObjectMapper();
23 final JsonFactory jsonFactory = mapper.getFactory();
24 final JsonGenerator generator = jsonFactory.createGenerator(output, JsonEncoding.UTF8);
25 generator.writeTree(jsonObject);
26 output.flush();
27 output.close();
28 // we have a byte array of jsonObject, so now create an HttpEntity with our byte array
29 final ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
30 final HttpPost post = new HttpPost(url);
31 post.setEntity(new InputStreamEntity(input, ContentType.APPLICATION_JSON));
32
Ensuring that the server sent you a 2xx response
If you got a 2xx response status code, your request was successful. To check the response status code:
1 final int statusCode = resp.getStatusLine().getStatusCode();
2 if (200 <= statusCode && statusCode < 300) {
3 // request succeeded!
4 } else {
5 // request failed :(
6 }
7
Reading the response
The response data from the server is stored in an HttpEntity object. The response data can be retrieved like so:
1 import org.apache.http.HttpEntity;
2
3 ...
4
5 final HttpEntity entity = resp.getEntity();
6 final String contentType = entity.getContentType() == null ? null : entity.getContentType().getValue();
7 final String charset = entity.getContentEncoding() == null ? null : entity.getContentEncoding().getValue();
8 final InputStream input = entity.getContent();
9 // read the input here
10
Reading the response as a JSON object
Now that you have the response's encoding (charset) and a stream of bytes (input), you can read the response as a Jackson JSON object.
1 import com.fasterxml.jackson.databind.ObjectMapper;
2 import com.fasterxml.jackson.databind.JsonNode;
3
4 ...
5
6 final ObjectMapper mapper = new ObjectMapper();
7 final JsonNode responseAsJson = mapper.readValue(input, JsonNode.class);
8
Reading the response as a String
This is complicated! You have to read the input to a byte array, then use that to construct a String. You can also use Apache IO Commons library's IOUtils.toString method.
1 final ByteArrayOutputStream output = new ByteArrayOutputStream();
2 final byte[] buffer = new byte[1024];
3 while (true) {
4 final int len = input.read(buffer);
5 if (len < 0)
6 break;
7 output.write(buffer, 0, len);
8 }
9 final String responseAsString = new String(output.toByteArray(), Charset.forName(charset == null ? "UTF-8" : charset));
10
Adding support for multiple, concurrent connections
1 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
2
3 ...
4
5 final PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager();
6 connMgr.setDefaultMaxPerRoute(16 /* this should match the number of concurrent connections you want to support */);
7 final CloseableHttpClient client =
8 HttpClientBuilder.create()
9 .setConnectionManager(connMgr)
10 .build();
11
Help
How to add plug-in specific help to the Cytoscape main help system?
If you download CreateHelp, compile and instal it through the App Manager of your running Cytoscape, you will see topic Sample 24 help is added to your help panel (Help --> Contents...). In order to make it simpler, you can download the help directory and copy this folder to your project's resources folder. Next you need to get the CyHelpBroker service in your CyActivator class. The code in CreateHelp:
1 import java.util.Properties;
2
3 import org.cytoscape.application.swing.CyHelpBroker;
4 import org.cytoscape.service.util.AbstractCyActivator;
5 import org.osgi.framework.BundleContext;
6
7 public class CyActivator extends AbstractCyActivator {
8
9 @Override
10 public void start(BundleContext context) throws Exception {
11
12 CyHelpBroker cyHelpBroker = getService(context, CyHelpBroker.class);
13
14 AppHelp action = new AppHelp(cyHelpBroker);
15 registerAllServices(context, action, new Properties());
16 }
17
18 }
19
In the main entry point of your plug-in, add the following function:
1 ...
2 import java.net.URL;
3 import org.cytoscape.application.swing.CyHelpBroker;
4 import javax.help.HelpSet;
5 ...
6 /**
7 * Hook plugin help into the Cytoscape main help system:
8 */
9 private void addHelp() {
10 final String HELP_SET_NAME = "/help/jhelpset";
11 final ClassLoader classLoader = AppHelp.class.getClassLoader();
12 URL helpSetURL;
13 try {
14 helpSetURL = HelpSet.findHelpSet(classLoader, HELP_SET_NAME);
15 final HelpSet newHelpSet = new HelpSet(classLoader, helpSetURL);
16 cyHelpBroker.getHelpSet().add(newHelpSet);
17 } catch (final Exception e) {
18 System.err.println("Sample24: Could not find help set: \"" + HELP_SET_NAME + "!");
19 }
20 }
21
Invoke this function somewhere in the constructor for the class. Finally, edit the files in the help directory. Most likely you would want to edit the files named jhelpmap.jhm and Topic.html. You may consider removing both, SubTopic.html and the reference to it in jhelpmap.jhm. You can also add additional topics, if you want. The effect this will have is to add a new topic at the very bottom of the Cytoscape help index. You may have a help button in your plug-in, in which case you would probably want to add code similar to the following:
1 ...
2 import cytoscape.view.CyHelpBroker;
3 ...
4 CyHelpBroker.getHelpBroker().enableHelpOnButton(helpButton, "Topic", null);
5 ...
6
That should be all that is needed to dynamically add your own help topic(s)!
OSGi
An easier way to construct service properties
Writing code to put in service properties requires a lot of boilerplate. Here's an example:
1 public void start(BundleContext bc) {
2 Properties props = new Properties();
3 props.put(TITLE, "My app");
4 props.put(PREFERRED_MENU, "Apps");
5 TaskFactory myTaskFactory = ...;
6 registerService(bc, myTaskFactory, props);
7 }
8
To avoid having to construct a Properties object manually for each service, you can use the ezProps method:
1 private static Properties ezProps(String... vals) {
2 final Properties props = new Properties();
3 for (int i = 0; i < vals.length; i += 2)
4 props.put(vals[i], vals[i + 1]);
5 return props;
6 }
7
The example above can be reworked using ezProps:
1 public void start(BundleContext bc) {
2 TaskFactory myTaskFactory = ...;
3 registerService(bc, myTaskFactory, ezProps(
4 TITLE, "My app",
5 PREFERRED_MENU, "Apps"));
6 }
7
Including packages accessed via reflection (e.g. JDBC, SAX parsers, XML utilities)
Some packages are not included in your Import-Package header in your manifest file. The maven-bundle-plugin or bndtool would normally generate this information for you. However, since both tools compute that metadata based on direct references in the compiled class files, they cannot detect packages that are accessed via reflection (e.g. using Class.forName). The old Java service model (which is used by SAX parsers, XML utilities and JDBC drivers) uses this a lot. The preferred option for getting around this is to manually add the problematic packages to your Import-Package header. For example, for the jdbc driver package, modify the maven-bundle-plugin configuration as follows:
<!-- Generates the OSGi metadata based on the osgi.bnd file. --> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <version>2.3.7</version> <extensions>true</extensions> <configuration> <instructions> <Bundle-SymbolicName>${bundle.symbolicName}</Bundle-SymbolicName> <Bundle-Version>${project.version}</Bundle-Version> <Export-Package>${bundle.namespace}</Export-Package> <Private-Package>${bundle.namespace}.internal.*</Private-Package> <Bundle-Activator>${bundle.namespace}.internal.CyActivator</Bundle-Activator> <Embed-Dependency>mysql-connector-java;inline=true</Embed-Dependency> <Import-Package>com.mysql.jdbc;version:=5.1.25,*;resolution:=optional</Import-Package> </instructions> </configuration> </plugin>
(note: option "inline=true" in the Embed-Dependency sometimes causes bundle fail to load; so, try with and without it) - and add the dependency as usual
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> <optional>true</optional> </dependency>
Embedding Dependencies
Sometimes, an app may depend on third-party jars. Third-party jars are not provided by Cytoscape core, nor from the OSGi framework. But they must be load into OSGi framework in order to run the app which depends on other jars. There is a feature request for Cytoscape App store to handle such dependencies. Before this feature is implemented in the future release of Cytoscape, for now app developer can do a work around -- use embedding dependencies. That means emebed dpendency jar into the App jar when building the App jar. The disadvantage of this approach is that your app jar will become fat, but this makes sure your app will get started smoothly in the Cytoscape without the dependency not found error.
To embed 3rd-party jars into app jar, in your pom.xml, you need to give instruction to the maven-bundle-plugin, for example, the folloing configuration will add two 3rd-party jars (colt.jar and currentent.jar) into the app jar,
1 <build>
2 <plugins>
3 ...
4 <plugin>
5 <groupId>org.apache.felix</groupId>
6 <artifactId>maven-bundle-plugin</artifactId>
7 ...
8 <configuration>
9 <instructions>
10 ...
11 <Embed-Dependency>colt,concurrent;scope=compile|runtime</Embed-Dependency>
12 </instructions>
13 </configuration>
14 </plugin>
15 ...
16 </plugins>
17 </build>
18
With this Embed-Dependency instruction in the configuration of maven-bundle-plugin, if your command 'mvn clean install' executes successful, you app jar will include the 3-party jars in itself. You can also there add <Embed-Transitive>true</Embed-Transitive> instruction, rather than guessing and adding each dependency manually, but this might bring unwanted and conflicting java libraries, which, however, can be fixed by also defining <exclusions> to some dependencies in your project.