diff --git a/pom.xml b/pom.xml
index 9ec2b0b4..64d58b31 100644
--- a/pom.xml
+++ b/pom.xml
@@ -33,7 +33,8 @@
Cytoscape Releases
http://code.cytoscape.org/nexus/content/repositories/releases/
-
+
+
miredot
MireDot Releases
@@ -111,9 +112,6 @@
@@ -127,7 +125,7 @@
412
put,post
- Invalid JSON/XML input.
+ Invalid JSON input.
@@ -135,6 +133,39 @@
+
+
+
+
+ org.eclipse.m2e
+ lifecycle-mapping
+ 1.0.0
+
+
+
+
+
+ com.qmino
+
+ miredot-maven-plugin
+
+
+ [1.4,)
+
+
+ restdoc
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/org/cytoscape/rest/internal/model/TableCell.java b/src/main/java/org/cytoscape/rest/internal/model/TableCell.java
new file mode 100644
index 00000000..298a7665
--- /dev/null
+++ b/src/main/java/org/cytoscape/rest/internal/model/TableCell.java
@@ -0,0 +1,15 @@
+package org.cytoscape.rest.internal.model;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+/**
+ * Wrapper class for a cell in CyTable
+ *
+ * @author kono
+ *
+ */
+//@JsonSerialize(using=)
+public class TableCell {
+
+
+}
diff --git a/src/main/java/org/cytoscape/rest/internal/serializer/GraphObjectSerializer.java b/src/main/java/org/cytoscape/rest/internal/serializer/GraphObjectSerializer.java
index c6f2346c..4010c084 100644
--- a/src/main/java/org/cytoscape/rest/internal/serializer/GraphObjectSerializer.java
+++ b/src/main/java/org/cytoscape/rest/internal/serializer/GraphObjectSerializer.java
@@ -193,6 +193,8 @@ private final void serializeRow(JsonGenerator generator, final CyIdentifiable ob
final Long targetId = ((CyEdge) obj).getSource().getSUID();
generator.writeNumberField("source", sourceId);
generator.writeNumberField("target", targetId);
+ } else {
+ generator.writeNumberField("id", obj.getSUID());
}
for (final CyColumn col : columns) {
final Object value = values.get(col.getName());
diff --git a/src/main/java/org/cytoscape/rest/internal/service/AbstractDataService.java b/src/main/java/org/cytoscape/rest/internal/service/AbstractDataService.java
index cbd2b927..7d9ad284 100644
--- a/src/main/java/org/cytoscape/rest/internal/service/AbstractDataService.java
+++ b/src/main/java/org/cytoscape/rest/internal/service/AbstractDataService.java
@@ -3,10 +3,12 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
-import java.util.Properties;
+import javax.ws.rs.NotFoundException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import org.cytoscape.application.CyApplicationManager;
import org.cytoscape.io.read.InputStreamTaskFactory;
@@ -41,7 +43,21 @@
public abstract class AbstractDataService {
// TODO: do we need this level of version granularity?
- protected static final String API_VERSION = "1.0.0";
+ protected static final String API_VERSION = "v1";
+
+ protected static final String ERROR_TAG = "\"error\":";
+
+
+ // Utilities to build error messages.
+
+ protected final WebApplicationException getError(final String errorMessage, final Status status) {
+ return new NotFoundException(Response.status(status)
+ .entity("{" + ERROR_TAG + "\"" + errorMessage + "\"}").build());
+ }
+
+ protected final WebApplicationException getError(final Exception ex, final Status status) {
+ return new NotFoundException(Response.status(status).entity(ex).build());
+ }
@Context
@@ -100,12 +116,12 @@ public AbstractDataService() {
protected final CyNetwork getCyNetwork(final Long id) {
if(id == null) {
- throw new WebApplicationException("Network SUID is null.", 500);
+ throw getError("SUID is null.", Response.Status.NOT_FOUND);
}
final CyNetwork network = networkManager.getNetwork(id);
if (network == null) {
- throw new WebApplicationException("Could not find network with SUID: " + id, 404);
+ throw getError("Could not find network with SUID: " + id, Response.Status.NOT_FOUND);
}
return network;
}
@@ -123,7 +139,7 @@ protected final Collection getCyNetworkViews(final Long id) {
protected final CyNode getNode(final CyNetwork network, final Long nodeId) {
final CyNode node = network.getNode(nodeId);
if (node == null) {
- throw new WebApplicationException("Could not find object: " + nodeId, 404);
+ throw getError("Could not find node with SUID: " + nodeId, Response.Status.NOT_FOUND);
}
return node;
}
diff --git a/src/main/java/org/cytoscape/rest/internal/service/AlgorithmicService.java b/src/main/java/org/cytoscape/rest/internal/service/AlgorithmicService.java
index e2cb6fc0..b09a51ef 100644
--- a/src/main/java/org/cytoscape/rest/internal/service/AlgorithmicService.java
+++ b/src/main/java/org/cytoscape/rest/internal/service/AlgorithmicService.java
@@ -1,20 +1,15 @@
package org.cytoscape.rest.internal.service;
-import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
-import javax.activation.MimetypesFileTypeMap;
import javax.inject.Singleton;
import javax.ws.rs.GET;
-import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
-import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -27,105 +22,137 @@
import org.cytoscape.work.TaskIterator;
import org.cytoscape.work.TaskMonitor;
+
+
+/**
+ * Algorithmic resources. Essentially, this is a high-level task executor.
+ *
+ * @author kono
+ *
+ */
@Singleton
@Path("/v1/apply")
public class AlgorithmicService extends AbstractDataService {
@Context
private TaskMonitor headlessTaskMonitor;
-
+
@Context
private CyLayoutAlgorithmManager layoutManager;
+
+ /**
+ *
+ * @summary Apply layout to a network
+ *
+ * @param algorithmName Name of layout algorithm ("circular", "force-directed", etc.)
+ * @param networkId Target network SUID
+ *
+ * @return Success message
+ */
@GET
- @Path("/layouts/{algorithmName}/{targetId}")
+ @Path("/layouts/{algorithmName}/{networkId}")
@Produces(MediaType.APPLICATION_JSON)
- public String applyLayout(@PathParam("algorithmName") String algorithmName, @PathParam("targetId") Long targetId) {
-
- final CyNetwork network = getCyNetwork(targetId);
- Collection views = this.networkViewManager.getNetworkViews(network);
+ public Response applyLayout(@PathParam("algorithmName") String algorithmName, @PathParam("networkId") Long networkId) {
+ final CyNetwork network = getCyNetwork(networkId);
+ final Collection views = this.networkViewManager.getNetworkViews(network);
if (views.isEmpty()) {
- throw new NotFoundException("View is not available for the network " + targetId);
+ throw getError("Could not find view for the network with SUID: " + networkId, Response.Status.NOT_FOUND);
}
final CyNetworkView view = views.iterator().next();
final CyLayoutAlgorithm layout = this.layoutManager.getLayout(algorithmName);
-
+ if(layout == null) {
+ throw getError("No such layout algorithm: " + algorithmName, Response.Status.NOT_FOUND);
+ }
+
final TaskIterator itr = layout.createTaskIterator(view, layout.getDefaultLayoutContext(),
CyLayoutAlgorithm.ALL_NODE_VIEWS, "");
try {
itr.next().run(headlessTaskMonitor);
} catch (Exception e) {
- e.printStackTrace();
+ throw getError(e, Response.Status.INTERNAL_SERVER_ERROR);
}
- return "{\"status\":\"OK\"}";
+ return Response.status(Response.Status.OK).entity("{\"message\":\"Layout finished.\"}").build();
}
+
+
+ /**
+ * Apply Visual Style to a network.
+ *
+ * @param styleName Visual Style name (title)
+ * @param networkId Target network SUID
+ *
+ * @return Success message.
+ */
@GET
- @Path("/styles/{styleName}/{targetId}")
+ @Path("/styles/{styleName}/{networkId}")
@Produces(MediaType.APPLICATION_JSON)
- public String applyStyle(@PathParam("styleName") String styleName, @PathParam("targetId") Long targetId) {
+ public Response applyStyle(@PathParam("styleName") String styleName, @PathParam("networkId") Long networkId) {
- final CyNetwork network = getCyNetwork(targetId);
+ final CyNetwork network = getCyNetwork(networkId);
final Set styles = vmm.getAllVisualStyles();
VisualStyle targetStyle = null;
- for(final VisualStyle style:styles) {
+ for (final VisualStyle style : styles) {
final String name = style.getTitle();
- if(name.equals(styleName)) {
+ if (name.equals(styleName)) {
targetStyle = style;
break;
}
}
- if(targetStyle == null) {
- throw new NotFoundException("Could not find Visual Style: " + styleName);
+
+ if (targetStyle == null) {
+ throw getError("Visual Style does not exist: " + styleName, Response.Status.NOT_FOUND);
}
-
+
Collection views = this.networkViewManager.getNetworkViews(network);
if (views.isEmpty()) {
- throw new NotFoundException("View is not available for the network " + targetId);
+ throw getError("Network view does not exist for the network with SUID: " + networkId, Response.Status.NOT_FOUND);
}
-
final CyNetworkView view = views.iterator().next();
vmm.setVisualStyle(targetStyle, view);
vmm.setCurrentVisualStyle(targetStyle);
targetStyle.apply(view);
-
- return "{\"status\":\"OK\"}";
+
+ return Response.status(Response.Status.OK).entity("{\"message\":\"Visual Style applied.\"}").build();
}
+ /**
+ * @summary Get list of available layout algorithm names
+ *
+ * @return List of layout algorithm names.
+ */
@GET
@Path("/layouts")
@Produces(MediaType.APPLICATION_JSON)
- public String getLayouts() {
+ public Collection getLayoutNames() {
final Collection layouts = layoutManager.getAllLayouts();
- final List layoutNames= new ArrayList();
- for(final CyLayoutAlgorithm layout: layouts) {
+ final List layoutNames = new ArrayList();
+ for (final CyLayoutAlgorithm layout : layouts) {
layoutNames.add(layout.getName());
}
- try {
- return getNames(layoutNames);
- } catch (IOException e) {
- throw new WebApplicationException(e, 500);
- }
+ return layoutNames;
}
+
-
-
+ /**
+ * @summary Get list of all Visual Style names.
+ *
+ * @return List of Style names.
+ *
+ */
@GET
- @Path("/images/{image}")
- @Produces("image/*")
- public Response getImage(@PathParam("image") String image) {
- // TODO implement this
- File f = new File(image);
-
-
- if (!f.exists()) {
- throw new WebApplicationException(404);
+ @Path("/styles")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Collection getStyleNames() {
+ final Set styles = vmm.getAllVisualStyles();
+ final List styleNames = new ArrayList();
+ for (final VisualStyle style : styles) {
+ styleNames.add(style.getTitle());
}
-
- final String mt = new MimetypesFileTypeMap().getContentType(f);
- return Response.ok(f, mt).build();
+ return styleNames;
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/org/cytoscape/rest/internal/service/NetworkDataService.java b/src/main/java/org/cytoscape/rest/internal/service/NetworkDataService.java
index 14d4361d..154bfb2a 100644
--- a/src/main/java/org/cytoscape/rest/internal/service/NetworkDataService.java
+++ b/src/main/java/org/cytoscape/rest/internal/service/NetworkDataService.java
@@ -27,6 +27,7 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
import org.cytoscape.io.read.CyNetworkReader;
import org.cytoscape.io.write.CyWriter;
@@ -302,7 +303,7 @@ public String getEdge(@PathParam("networkId") Long networkId, @PathParam("edgeId
final CyNetwork network = getCyNetwork(networkId);
final CyEdge edge = network.getEdge(edgeId);
if (edge == null) {
- throw new NotFoundException("Could not find edge with SUID: " + edgeId);
+ throw getError("Could not find edge with SUID: " + edgeId, Response.Status.NOT_FOUND);
}
return getGraphObject(network, edge);
}
@@ -854,11 +855,11 @@ private final String getNetworkString(final CyNetwork network) {
CyWriter writer = cytoscapeJsWriterFactory.createWriter(stream, network);
String jsonString = null;
try {
- writer.run(null);
+ writer.run(new HeadlessTaskMonitor());
jsonString = stream.toString();
stream.close();
} catch (Exception e) {
- e.printStackTrace();
+ throw getError(e, Response.Status.PRECONDITION_FAILED);
}
return jsonString;
}
diff --git a/src/main/java/org/cytoscape/rest/internal/service/RootService.java b/src/main/java/org/cytoscape/rest/internal/service/RootService.java
new file mode 100644
index 00000000..bdb602ac
--- /dev/null
+++ b/src/main/java/org/cytoscape/rest/internal/service/RootService.java
@@ -0,0 +1,44 @@
+package org.cytoscape.rest.internal.service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.inject.Singleton;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+/**
+ * Root of the REST API server.
+ *
+ * @summary Simply displays available API versions.
+ *
+ * @author kono
+ *
+ */
+@Singleton
+@Path("/")
+public class RootService extends AbstractDataService {
+
+ private static final String[] VERSION_LIST = { API_VERSION };
+ private static final Map VERSION_MAP = new HashMap();
+ static {
+ VERSION_MAP.put("availableApiVersions", VERSION_LIST);
+ }
+
+ /**
+ * @summary Get available REST API versions
+ *
+ * @return List of available REST API versions. Currently, v1 is the only
+ * available version.
+ *
+ */
+ @GET
+ @Path("/")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Map getVersions() {
+ return VERSION_MAP;
+ }
+
+}
diff --git a/src/main/java/org/cytoscape/rest/internal/service/TableDataService.java b/src/main/java/org/cytoscape/rest/internal/service/TableDataService.java
index 3006aea8..8bedee6d 100644
--- a/src/main/java/org/cytoscape/rest/internal/service/TableDataService.java
+++ b/src/main/java/org/cytoscape/rest/internal/service/TableDataService.java
@@ -19,6 +19,8 @@
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.xml.ws.soap.AddressingFeature.Responses;
import org.cytoscape.model.CyColumn;
import org.cytoscape.model.CyIdentifiable;
@@ -95,8 +97,7 @@ public void createColumn(@PathParam("networkId") Long networkId, @PathParam("tab
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
tableMapper.createNewColumn(rootNode, table);
} catch (IOException e) {
- logger.error("Failed to create column.", e);
- throw new WebApplicationException("Could not create column.", e, 500);
+ throw getError(e, Response.Status.PRECONDITION_FAILED);
}
}
@@ -226,29 +227,76 @@ public String getRow(@PathParam("networkId") Long networkId, @PathParam("tableTy
@PathParam("primaryKey") Long primaryKey) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType);
+ if(!table.rowExists(primaryKey)) {
+ throw getError("Could not find the row with primary key: " + primaryKey, Response.Status.NOT_FOUND);
+ }
+
final CyRow row = table.getRow(primaryKey);
+
try {
return this.serializer.serializeRow(row);
} catch (IOException e) {
logger.error("Copuld not serialize a table.");
- throw new WebApplicationException("Could not serialize the table.", e, 500);
+ throw getError(e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
+ /**
+ *
+ * Get a cell entry
+ *
+ * @param networkId
+ * Network SUID
+ * @param tableType
+ * Table type (defaultnode, defaultedge or defaultnetwork)
+ * @param primaryKey
+ * Name of primary key column
+ * @param columnName
+ * Name of the column
+ *
+ * @return Value in the cell. String, Boolean, Number, or List.
+ *
+ */
@GET
@Path("/{tableType}/rows/{primaryKey}/{columnName}")
@Produces(MediaType.APPLICATION_JSON)
- public String getCell(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType,
+ public Object getCell(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType,
@PathParam("primaryKey") Long primaryKey, @PathParam("columnName") String columnName) {
final CyNetwork network = getCyNetwork(networkId);
+
final CyTable table = getTableByType(network, tableType);
+ if(!table.rowExists(primaryKey)) {
+ throw getError("Could not find the row with promary key: " + primaryKey, Response.Status.NOT_FOUND);
+ }
+
+ final CyColumn column = table.getColumn(columnName);
+ if(column == null) {
+ throw getError("Could not find the column: " + columnName, Response.Status.NOT_FOUND);
+ }
+
final CyRow row = table.getRow(primaryKey);
- try {
- return this.serializer.serializeCell(row, columnName);
- } catch (IOException e) {
- throw new WebApplicationException(e, 500);
+
+ if (column.getType() == List.class) {
+ List> listCell = row.getList(columnName, column.getListElementType());
+ if (listCell == null) {
+ throw getError("Could not find list value.",Response.Status.NOT_FOUND);
+ } else {
+ return listCell;
+ }
+ } else {
+ final Object cell = row.get(columnName, column.getType());
+ if (cell == null) {
+ throw getError("Could not find value." ,Response.Status.NOT_FOUND);
+ }
+
+ if (column.getType() == String.class) {
+ return cell.toString();
+ } else {
+ return cell;
+ }
}
}
+
@GET
@Path("/{tableType}/rows")
diff --git a/src/main/java/org/cytoscape/rest/internal/task/GrizzlyServerManager.java b/src/main/java/org/cytoscape/rest/internal/task/GrizzlyServerManager.java
index 57f0d961..4405c9d5 100644
--- a/src/main/java/org/cytoscape/rest/internal/task/GrizzlyServerManager.java
+++ b/src/main/java/org/cytoscape/rest/internal/task/GrizzlyServerManager.java
@@ -11,6 +11,7 @@
import org.cytoscape.rest.internal.service.MiscDataService;
import org.cytoscape.rest.internal.service.NetworkDataService;
import org.cytoscape.rest.internal.service.NetworkViewDataService;
+import org.cytoscape.rest.internal.service.RootService;
import org.cytoscape.rest.internal.service.StyleService;
import org.cytoscape.rest.internal.service.TableDataService;
import org.glassfish.grizzly.http.server.HttpServer;
@@ -57,6 +58,7 @@ public void startServer() throws Exception {
if (server == null) {
final URI baseURI = UriBuilder.fromUri(baseURL).port(portNumber).build();
final ResourceConfig rc = new ResourceConfig(
+ RootService.class,
NetworkDataService.class,
NetworkViewDataService.class,
TableDataService.class,
@@ -68,7 +70,7 @@ public void startServer() throws Exception {
.register(JacksonFeature.class);
this.server = GrizzlyHttpServerFactory.createHttpServer(baseURI, rc);
- logger.info("Cytoscape RESTful API service started. Listening at port: " + portNumber);
+ logger.info("========== Cytoscape RESTful API service started. Listening at port: " + portNumber + " ==============");
}
}