package org.cytoscape.rest.internal.resource;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.cytoscape.model.CyColumn;
import org.cytoscape.model.CyEdge;
import org.cytoscape.model.CyNetwork;
import org.cytoscape.model.CyNode;
import org.cytoscape.model.CyRow;
import org.cytoscape.model.CyTable;
import org.cytoscape.rest.internal.datamapper.TableMapper;
import org.cytoscape.rest.internal.serializer.CyTableSerializer;
import org.cytoscape.rest.internal.serializer.TableModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* REST API for CyTable objects. This is for assigned table only.
*
*/
@Singleton
@Path("/v1/networks/{networkId}/tables")
public class TableResource extends AbstractResource {
private final static Logger logger = LoggerFactory.getLogger(TableResource.class);
private static enum TableType {
DEFAULT_NODE("defaultnode"), DEFAULT_EDGE("defaultedge"), DEFAULT_NETWORK("defaultnetwork");
private final String type;
private TableType(final String type) {
this.type = type;
}
private String getType() {
return this.type;
}
}
private final TableMapper tableMapper;
private final ObjectMapper tableObjectMapper;
private final CyTableSerializer tableSerializer;
public TableResource() {
super();
this.tableMapper = new TableMapper();
this.tableObjectMapper = new ObjectMapper();
this.tableObjectMapper.registerModule(new TableModule());
this.tableSerializer = new CyTableSerializer();
}
/**
* Create new, empty column in an assigned table.
* This accepts the following object OR allay of this objects:
*
* <pre>
* {
* "name":"COLUMN NAME",
* "type":"data type, Double, String, Boolean, Long, Integer",
* "immutable": "Optional: boolean value to specify immutable or not",
* "list": "Optional. If true, return create List column for the given type."
* "local": "Optional. If true, it will be a local column"
* }
* </pre>
*
* @summary Create new column(s) in the table
*
*
* @param networkId
* Network SUID
* @param tableType
* Table type: "defaultnode", "defaultedge" or "defaultnetwork"
*
*/
@POST
@Path("/{tableType}/columns")
@Consumes(MediaType.APPLICATION_JSON)
public Response createColumn(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
final InputStream is) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
final CyTable localTable = getTableByType(
network, tableType, JsonTags.COLUMN_IS_LOCAL);
final ObjectMapper objMapper = new ObjectMapper();
try {
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
if(rootNode.isArray()) {
for(JsonNode node: rootNode) {
tableMapper.createNewColumn(node, table, localTable);
}
} else {
tableMapper.createNewColumn(rootNode, table, localTable);
}
// Use 201 for created resource
return Response.status(Response.Status.CREATED).build();
} catch (Exception e) {
throw getError("Could not process column JSON.",
e, Response.Status.PRECONDITION_FAILED);
}
}
/**
*
* @summary Delete a column in a table
*
* @param networkId
* Network SUID
* @param tableType
* Table type: "defaultnode", "defaultedge" or "defaultnetwork"
* @param columnName
* Name of the column to be deleted
*
*/
@DELETE
@Path("/{tableType}/columns/{columnName}")
public Response deleteColumn(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
@PathParam("columnName") String columnName) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
if (table != null) {
table.deleteColumn(columnName);
return Response.ok().build();
} else {
logger.error("Failed to delete a column. (Missing table?)");
throw new NotFoundException("Could not find the table. (This should not happen!)");
}
}
/**
* To update the column name, you need to provide the parameters in the body:
*
* <pre>
* {
* "oldName": OLD_COLUMN_NAME,
* "newName": NEW_COLUMN_NAME
* }
* </pre>
*
* Both parameters are required.
*
*
* @summary Update a column name
*
* @param networkId
* Network SUID
* @param tableType
* Table type: "defaultnode", "defaultedge" or "defaultnetwork"
*
*/
@PUT
@Path("/{tableType}/columns")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateColumnName(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
final InputStream is) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
final ObjectMapper objMapper = new ObjectMapper();
try {
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
tableMapper.updateColumnName(rootNode, table);
return Response.ok().build();
} catch (Exception e) {
throw getError("Could not parse the input JSON for updating "
+ "column name.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Update values in a column
*
* By default, you need to provide key-value pair to set values.
* However, if "default" is provided, it will be used for the entire column.
*
* This is useful to set columns like "selected."
*
*
* @param networkId
* Network SUID
* @param tableType
* Table type: "defaultnode", "defaultedge" or "defaultnetwork"
* @param columnName
* Target column name
* @param default
* Optional. If this value is provided, all cells will be set to this.
*
*/
@PUT
@Path("/{tableType}/columns/{columnName}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateColumnValues(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
@PathParam("columnName") String columnName,
@QueryParam("default") String defaultValue, final InputStream is) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
if (defaultValue != null) {
tableMapper.updateAllColumnValues(defaultValue, table, columnName);
} else {
final ObjectMapper objMapper = new ObjectMapper();
try {
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
tableMapper.updateColumnValues(rootNode, table, columnName);
} catch (IOException e) {
throw getError(
"Could not parse the input JSON for updating column values.",
e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
return Response.ok().build();
}
/**
*
* This API is for updating default node/edge/network data table. New columns will be created if they
* does not exist in the target table.
*
* The BODY of the data should be in the following format:<br/>
*
* <pre>
* {
* "key":"SUID", // This is the unique key column in the existing table
* "dataKey": "id", // Mapping key for the new values
* "data": [
* {
* "id": 12345, // Required. Field name should be same as "dataKey."
* // In this case, it is "id," but can be anything.
* "gene_name": "brca1",
* "exp1": 0.11,
* "exp2": 0.2
* }, ...
*
* ]
* }
* </pre>
*
* Current limitations:
* <ul>
* <li> If key is not specified, SUID will be used for mapping</li>
* <li>Numbers are handled as Double</li>
* <li>List column is not supported in this version</li>
* </ul>
*
* @summary Update default table with new values.
*
* @param networkId
* Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
* @param class Optional. If this query parameter is set to local,
* local table column will be updated.
*/
@PUT
@Path("/{tableType}")
@Consumes(MediaType.APPLICATION_JSON)
public Response updateTable(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
@QueryParam("class") String tableClass,
final InputStream is) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, tableClass);
final ObjectMapper objMapper = new ObjectMapper();
try {
// This should be an JSON array.
final JsonNode rootNode = objMapper.readValue(is, JsonNode.class);
tableMapper.updateTableValues(rootNode, table);
} catch (Exception e) {
throw getError("Could not parse the input JSON for updating table because: " + e.getMessage(), e, Response.Status.INTERNAL_SERVER_ERROR);
}
return Response.ok().build();
}
/**
* @summary Get a row in a table
*
* @param networkId
* Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
* @param primaryKey
* Name of primary key column
*
* @return A row in the table
*/
@GET
@Path("/{tableType}/rows/{primaryKey}")
@Produces(MediaType.APPLICATION_JSON)
public String getRow(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
@PathParam("primaryKey") Long primaryKey) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
if (!table.rowExists(primaryKey)) {
throw new NotFoundException("Could not find the row "
+ "with primary key: " + primaryKey);
}
final CyRow row = table.getRow(primaryKey);
try {
return this.serializer.serializeRow(row);
} catch (IOException e) {
throw getError("Could not serialize a row with primary key: " + primaryKey, e,
Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Get a value in the cell
*
* @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 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, null);
if (!table.rowExists(primaryKey)) {
throw new NotFoundException("Could not find the row with promary key: " + primaryKey);
}
final CyColumn column = table.getColumn(columnName);
if (column == null) {
throw new NotFoundException("Could not find the column: " + columnName);
}
final CyRow row = table.getRow(primaryKey);
if (column.getType() == List.class) {
List<?> listCell = row.getList(columnName, column.getListElementType());
if (listCell == null) {
throw new NotFoundException("Could not find list value.");
} else {
return listCell;
}
} else {
final Object cell = row.get(columnName, column.getType());
if (cell == null) {
throw new NotFoundException("Could not find value.");
}
if (column.getType() == String.class) {
return cell.toString();
} else {
return cell;
}
}
}
/**
*
* @summary Get all rows in a table
*
* @param networkId Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
*
* @return All rows in the table
*
*/
@GET
@Path("/{tableType}/rows")
@Produces(MediaType.APPLICATION_JSON)
public String getRows(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
try {
return this.serializer.serializeAllRows(table.getAllRows());
} catch (IOException e) {
throw getError("Could not serialize rows.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Get all columns in a table
*
* @param networkId Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
*
* @return All columns in the specified table.
*
*/
@GET
@Path("/{tableType}/columns")
@Produces(MediaType.APPLICATION_JSON)
public String getColumnNames(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
try {
return this.serializer.serializeColumns(table.getColumns());
} catch (IOException e) {
throw getError("Could not serialize column names.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
*
* @summary Get all values in a column
*
* @param networkId Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
* @param columnName Column name
*
* @return All values under the specified column.
*
*/
@GET
@Path("/{tableType}/columns/{columnName}")
@Produces(MediaType.APPLICATION_JSON)
public String getColumnValues(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType,
@PathParam("columnName") String columnName) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
final CyColumn column = table.getColumn(columnName);
final List<Object> values = column.getValues(column.getType());
try {
return this.serializer.serializeColumnValues(column, values);
} catch (IOException e) {
throw getError("Could not serialize column values.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* @summary Get all tables assigned to the network
*
* @param networkId network SUID
*
* @return All tables in JSON
*/
@GET
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public String getTables(@PathParam("networkId") Long networkId) {
final Set<CyTable> tables = this.tableManager.getAllTables(true);
try {
return tableObjectMapper.writeValueAsString(tables);
} catch (IOException e) {
throw getError("Could not serialize tables.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
private final CyTable getTableByType(final CyNetwork network,
final String tableType, final String tableClass) {
// Check local or not
final Boolean isLocal;
if(tableClass == null || tableClass.isEmpty()) {
isLocal = false;
} else if(tableClass.equals("local")) {
isLocal = true;
} else {
isLocal = false;
}
CyTable table;
if (tableType.equals(TableType.DEFAULT_NODE.getType())) {
if(isLocal) {
table = network.getTable(CyNode.class, CyNetwork.LOCAL_ATTRS);
} else {
table = network.getDefaultNodeTable();
}
} else if (tableType.equals(TableType.DEFAULT_EDGE.getType())) {
if(isLocal) {
table = network.getTable(CyEdge.class, CyNetwork.LOCAL_ATTRS);
} else {
table = network.getDefaultEdgeTable();
}
} else if (tableType.equals(TableType.DEFAULT_NETWORK.getType())) {
if(isLocal) {
table = network.getTable(CyNetwork.class, CyNetwork.LOCAL_ATTRS);
} else {
table = network.getDefaultNetworkTable();
}
} else {
// No such table.
throw new NotFoundException("No such table type: " + tableType);
}
return table;
}
/**
* @summary Get a default table
*
* @param networkId Network SUID
* @param tableType Table type (defaultnode, defaultedge or defaultnetwork)
*
* @return The Table in JSON
*
*/
@GET
@Path("/{tableType}")
@Produces(MediaType.APPLICATION_JSON)
public String getTable(@PathParam("networkId") Long networkId,
@PathParam("tableType") String tableType) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
try {
return this.tableObjectMapper.writeValueAsString(table);
} catch (JsonProcessingException e) {
throw getError("Could not serialize table.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
/**
* @summary Get a table as CSV
*
* @param networkId
* Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
*
* @return Table in CSV format
*
*/
@GET
@Path("/{tableType}.csv")
@Produces(MediaType.TEXT_PLAIN)
public String getTableAsCsv(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType) {
return getTableString(networkId, tableType, ",");
}
/**
*
* @summary Get a table as TSV (tab delimited text)
*
* @param networkId
* Network SUID
* @param tableType
* Table type (defaultnode, defaultedge or defaultnetwork)
*
* @return Table in TSV format
*
*/
@GET
@Path("/{tableType}.tsv")
@Produces(MediaType.TEXT_PLAIN)
public String getTableAsTsv(@PathParam("networkId") Long networkId, @PathParam("tableType") String tableType) {
return getTableString(networkId, tableType, "\t");
}
/**
* Actual function to generate CSV/TSV
*
* @param networkId
* @param tableType
* @param separator
* @return
*/
private final String getTableString(final Long networkId,
final String tableType, final String separator) {
final CyNetwork network = getCyNetwork(networkId);
final CyTable table = getTableByType(network, tableType, null);
try {
final String result = tableSerializer.toCSV(table, separator);
return result;
} catch (Exception e) {
throw getError("Could not serialize table into CSV.", e, Response.Status.INTERNAL_SERVER_ERROR);
}
}
}