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 @@ RESTful API for Cytoscape ${rest.api.version} - - Disabled - @@ -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 + " =============="); } }