package gov.nih.ncgc.bard.rest;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import gov.nih.ncgc.bard.entity.Assay;
import gov.nih.ncgc.bard.entity.Experiment;
import gov.nih.ncgc.bard.entity.ExperimentData;
import gov.nih.ncgc.bard.entity.FitModel;
import gov.nih.ncgc.bard.entity.Project;
import gov.nih.ncgc.bard.rest.rowdef.AssayDefinitionObject;
import gov.nih.ncgc.bard.rest.rowdef.DataResultObject;
import gov.nih.ncgc.bard.rest.rowdef.DoseResponseResultObject;
import gov.nih.ncgc.bard.tools.Util;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
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 java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
/**
* Prototype of MLBD REST resources.
* <p/>
* This is mainly to explore the use of Jersey for presenting REST
* services for the MLBD
*
* @author Rajarshi Guha
*/
@Path("/exptdata")
public class BARDExperimentDataResource extends BARDResource<ExperimentData> {
static final Logger logger =
Logger.getLogger(BARDExperimentDataResource.class.getName());
public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss";
static final String VERSION = "1.0";
@Context
ServletContext servletContext;
@Context
HttpServletRequest httpServletRequest;
@Context
HttpHeaders headers;
@Override
public Class<ExperimentData> getEntityClass() {
return ExperimentData.class;
}
@Override
public String getResourceBase() {
return BARDConstants.API_BASE + "/exptdata";
}
@GET
@Produces("text/plain")
@Path("/_info")
public String info() {
StringBuilder msg = new StringBuilder("Returns experiment data information\n\nAvailable resources:\n");
List<String> paths = Util.getResourcePaths(this.getClass());
for (String path : paths) msg.append(path).append("\n");
msg.append("/exptdata/" + BARDConstants.API_EXTRA_PARAM_SPEC + "\n");
return msg.toString();
}
@GET
@Produces("text/plain")
@Path("/_count")
public String count(@QueryParam("filter") String filter) {
String ret;
try {
if (filter == null) {
ret = String.valueOf(db.getEntityCount(ExperimentData.class));
} else {
List<ExperimentData> experiments = db.searchForExperimentData(filter, -1, -1);
ret = String.valueOf(experiments.size());
}
db.closeConnection();
return ret;
} catch (SQLException e) {
throw new WebApplicationException(e, 500);
} catch (IOException e) {
throw new WebApplicationException(e, 500);
}
}
@GET
@Path("/")
public Response getResources(@QueryParam("filter") String filter,
@QueryParam("expand") String expand,
@QueryParam("skip") Integer skip,
@QueryParam("top") Integer top) {
boolean expandEntries = false;
if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes")))
expandEntries = true;
Response response;
try {
if (filter == null) {
if (countRequested) return Response.ok(String.valueOf(db.getEntityCount(ExperimentData.class)), MediaType.TEXT_PLAIN).build();
else return Response.status(413).build();
} else {
List<ExperimentData> experimentData = db.searchForExperimentData(filter, skip, top); // TODO search needs to be reworked
if (countRequested) return Response.ok(String.valueOf(experimentData.size()), MediaType.TEXT_PLAIN).build();
if (expandEntries) {
String json = Util.toJson(experimentData);
response = Response.ok(json, MediaType.APPLICATION_JSON).build();
} else {
List<String> links = new ArrayList<String>();
for (ExperimentData a : experimentData) links.add(a.getResourcePath());
String json = Util.toJson(links);
response = Response.ok(json, MediaType.APPLICATION_JSON).build();
}
}
db.closeConnection();
return response;
} catch (SQLException e) {
throw new WebApplicationException(e, 500);
} catch (IOException e) {
throw new WebApplicationException(e, 500);
}
}
/**
* Get multiple experiment data objects via POST'ing a list of ids.
* <p/>
* Currently this method ignores the TID specification of an experiment data identifier. That
* is it only considers identifiers of the form <code>eid.sid</code>.
* <p/>
* The return value only contains experiment data for the identifiers that actually had
* experiment data. That is, the number of experiment data entries in the response may be
* less than the number of identifiers specified.
*
* @param ids A comma separated list of experiment data identifiers
* @param filter A filter string. Currently ignored
* @param expand If <code>true</code> show detailed respose, else a compact response. Currently ignored
* @return
* @throws IOException
*/
@POST
@Path("/")
@Consumes("application/x-www-form-urlencoded")
public Response getExptDataByIds(@FormParam("sids") String ids,
@FormParam("cids") String cids,
@FormParam("eids") String eids,
@FormParam("aids") String aids,
@FormParam("pids") String pids,
@QueryParam("filter") String filter,
@QueryParam("top") Integer top,
@QueryParam("skip") Integer skip,
@QueryParam("expand") String expand) throws IOException, SQLException {
List<ExperimentData> edlist = new ArrayList<ExperimentData>();
List<String> edids = null;
if (skip == null) skip = -1;
if (top == null) top = -1;
if (cids != null) {
// if CIDs specified we get back corresponding SIDs and
// then set them in the ids variable. Implies that that
// if CIDs are supplied, ids is ignored
List<Long> sids = new ArrayList<Long>();
String[] toks = cids.split(",");
for (String cid : toks) sids.addAll(db.getSidsByCid(Long.parseLong(cid.trim())));
ids = Util.join(sids, ",");
logger.info("CIDs were specified. Converted " + toks.length + " CIDs to " + sids.size() + " SIDs");
}
if (ids != null && eids == null && aids == null && pids == null) {
// in this case, id's can be simple SID's in which case we have
// to find out which experiments they are tested in. Or else, they
// are of the form eid.sid and we don't need to do anything more
edids = new ArrayList<String>();
if (ids.indexOf(".") > 0) {
Collections.addAll(edids, ids.split(","));
logger.info("EID.SID specified.");
} else {
int nexpt = 0;
for (String id : ids.split(",")) {
List<Long> sEids = db.getSubstanceExperimentIds(Long.parseLong(id.trim()), -1, -1);
for (Long asEid : sEids) edids.add(asEid + "." + id);
nexpt += sEids.size();
}
logger.info("SIDs specified. Converted to " + edids.size() + " EID.SIDs across " + nexpt + " experiments");
}
} else if (ids != null && (eids != null || aids != null || pids != null)) {
// SID's specified and also specific experiment, assay or project
// is specified in this case, I don't think we need to do any
// filtering because we've already got a list of SIDs
logger.info("SIDs specified along with experiments, assays or projects");
if (eids != null) edids = getEdidFromExperiments(ids.split(","), eids.split(","), skip, top, filter);
else if (aids != null) edids = getEdidFromAssays(ids.split(","), aids.split(","), skip, top, filter);
else if (pids != null) edids = getEdidFromProjects(ids.split(","), pids.split(","), skip, top, filter);
} else if (eids != null || aids != null || pids != null) {
logger.info("No SIDs specified. Will retrieve all from experiments, assays or projects");
// no SID's specified. We have to pull relevant SID's from experiment, assays or projects
if (eids != null) edids = getAllEdidFromExperiments(eids.split(","), skip, top, filter);
else if (aids != null) edids = getAllEdidFromAssays(aids.split(","), skip, top, filter);
} else {
db.closeConnection();
throw new BadRequestException("If no SID's are specified, must provide one or experiment, assay or project identifiers");
}
// pull in the actual data - at this point we should have the filtered (but
// not sorted) list of experiment data ids
if (edids != null && edids.size() > 0) {
logger.info("Will work with " + edids.size() + " edids");
// we first pull in all edids - this is required in the presence of top/skip
// since if we apply top/skip to the edid list, it's possible that the remaining
// edids will not resolve to actual data. Changing the top/skip will include an
// edid(s) that do resolve to valid data. This behavior is confusing (especially so
// when the query started with CIDs which would resolve to multiple SIDs).
//
// As a result even though this is not good for performance, we pull back results
// and then apply top/skip. One way to improve performance is to save the full list
// to cache - as a result paging after the first query (on the same query params)
// should only pull from cache
// group the edids by experiment since the db method
// assumes all SIDs are from a given experiment
Map<String, List<String>> map = new HashMap<String, List<String>>();
for (String edid : edids) {
String eid = edid.split("\\.")[0];
if (map.containsKey(eid)) {
List<String> l = map.get(eid);
l.add(edid);
map.put(eid, l);
} else {
List<String> l = new ArrayList<String>();
l.add(edid);
map.put(eid, l);
}
}
edlist = new ArrayList<ExperimentData>();
for (String eid : map.keySet()) {
edlist.addAll(db.getExperimentDataByDataId(map.get(eid)));
}
// TODO we should do sort on edlist at this point
if (skip == -1) skip = 0;
if (top > 0) {
List<ExperimentData> tmp = new ArrayList<ExperimentData>();
// if we have N results and are told to skip N, we won't return any
// otherwise lets skip and take the top
if (edlist.size() - skip > 0) {
int ttop = top;
if (ttop > edlist.size()) ttop = edlist.size();
int end = skip + ttop;
if (end > edlist.size()) end = edlist.size();
for (int i = skip; i < end; i++) tmp.add(edlist.get(i));
}
edlist = tmp;
}
if (countRequested) {
return Response.ok(String.valueOf(edlist.size()), MediaType.TEXT_PLAIN).build();
}
}
db.closeConnection();
if (edlist.size() == 0)
return Response.status(404).entity("No data available for ids: " + ids).type(MediaType.TEXT_PLAIN).build();
else {
// we need to convert the JSON strings to a JSON array
StringBuilder sb = new StringBuilder("[");
String delim = "";
for (ExperimentData ed : edlist) {
sb.append(delim).append(ed.getResultJson());
delim = ",";
}
sb.append("]");
return Response.ok(sb.toString()).type(MediaType.APPLICATION_JSON).build();
}
}
private List<String> getAllEdidFromAssays(String[] aids, int skip, int top, String filter) throws SQLException {
List<String> edids = new ArrayList<String>();
for (String aid : aids) {
List<Experiment> eids = db.getAssayByAid(Long.parseLong(aid)).getExperiments();
String[] s = new String[eids.size()];
for (int i = 0; i < eids.size(); i++) s[i] = eids.get(i).getBardExptId().toString();
edids.addAll(getAllEdidFromExperiments(s, skip, top, filter));
}
db.closeConnection();
return edids;
}
private List<String> getAllEdidFromExperiments(String[] eids, int skip, int top, String filter) throws SQLException {
List<String> edids = new ArrayList<String>();
for (String eid : eids) {
edids.addAll(db.getExperimentDataIds(Long.parseLong(eid), skip, top, filter));
}
db.closeConnection();
return edids;
}
private List<String> getEdidFromProjects(String[] ids, String[] pids, int skip, int top, String filter) throws SQLException {
List<Long> eids = new ArrayList<Long>();
for (String pid : pids) {
Project project = db.getProject(Long.parseLong(pid));
eids.addAll(project.getEids());
}
db.closeConnection();
return getEdidFromExperiments(ids, eids.toArray(new String[0]), skip, top, filter);
}
private List<String> getEdidFromAssays(String[] ids, String[] aids, int skip, int top, String filter) throws SQLException {
List<Long> eids = new ArrayList<Long>();
for (String aid : aids) {
Assay assay = db.getAssayByAid(Long.parseLong(aid));
List<Experiment> expts = assay.getExperiments();
for (Experiment e : expts) eids.add(e.getBardExptId());
}
db.closeConnection();
return getEdidFromExperiments(ids, eids.toArray(new String[0]), skip, top, filter);
}
private List<String> getEdidFromExperiments(String[] ids, String[] eida, int skip, int top, String filter) {
List<String> edids = new ArrayList<String>();
for (String sid : ids) {
for (String eid : eida) edids.add(eid.trim() + "." + sid.trim());
}
return edids;
}
private List<ExperimentData> getExperimentData(String[] edids, String filter) throws SQLException {
List<ExperimentData> edlist = new ArrayList<ExperimentData>();
for (String edid : edids) {
try {
String exptId = "";
String[] tokens = edid.split("\\.");
if (tokens.length < 2) {
throw new Exception("Bogus experiment data id: " + edid);
} else if (tokens.length == 2) {
exptId = edid.trim();
} else {
exptId = tokens[0].trim() + "." + tokens[1].trim();
}
ExperimentData ed = db.getExperimentDataByDataId(exptId);
if (ed != null) edlist.add(ed);
} catch (Exception e) {
}
}
db.closeConnection();
return edlist;
}
@GET
@Path("/{edid}")
public Response getResources(@PathParam("edid") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) {
ExperimentData experimentData;
try {
String exptId = "";
String[] tokens = resourceId.split("\\.");
if (tokens.length < 2) {
throw new BadRequestException("Bogus experiment data id: " + resourceId);
} else if (tokens.length == 2) {
exptId = resourceId;
} else {
exptId = tokens[0] + "." + tokens[1];
}
//System.err.println("** "+httpServletRequest.getPathInfo()+": resourceId="+resourceId+" exptId="+exptId);
experimentData = db.getExperimentDataByDataId(exptId);
if (experimentData == null || experimentData.getExptDataId() == null)
return Response.status(404).entity("No data for " + resourceId).type("text/plain").build();
if (tokens.length == 2) {
return Response.ok(experimentData.getResultJson(), MediaType.APPLICATION_JSON).build();
}
int tid = Integer.parseInt(tokens[2]);
if (tid != 0) { // only keep the specific entry from readout[]
FitModel m = experimentData.getReadouts().get(tid - 1);
experimentData.setReadouts(Arrays.asList(m));
}
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
root.putPOJO("exptdata", experimentData);
ArrayNode array = root.putArray("results");
AssayDefinitionObject[] ado = experimentData.getDefs();
DataResultObject[] results = experimentData.getResults();
// check the tid; data tid are stored in column coordinate,
// so we need to offset by 8
if (tid == 0) { // return all?
for (AssayDefinitionObject d : ado) {
if ("DoseResponse".equals(d.getType())) {
// ignore dose response
continue;
}
tid = Integer.parseInt(d.getTid());
DataResultObject res = null;
for (DataResultObject r : results) {
if (tid == r.getTid()) {
res = r;
break;
}
}
if (res == null) {
logger.info("no matching result object for tid=" + tid);
continue;
}
ObjectNode node = array.addObject();
node.putPOJO("result", d);
Object value = res.getValue();
if (value instanceof String) {
value = ((String) value).replaceAll("\"", "");
if ("".equals(value)) {
value = null;
}
}
node.putPOJO("value", value);
}
} else {
AssayDefinitionObject def = null;
for (AssayDefinitionObject d : ado) {
if (tid == Integer.parseInt(d.getTid())) {
def = d;
break;
}
}
DataResultObject res = null;
for (DataResultObject r : results) {
if (tid == r.getTid()) {
res = r;
break;
}
}
ObjectNode node = array.addObject();
node.putPOJO("result", def);
if ("DoseResponse".equals(def.getType())) {
DoseResponseResultObject drObj = null;
for (DoseResponseResultObject dr :
experimentData.getDr()) {
if (tid == Integer.parseInt(dr.getTid())) {
drObj = dr;
break;
}
}
node.putPOJO("value", drObj);
} else {
Object value = res.getValue();
if (value instanceof String) {
value = ((String) value).replaceAll("\"", "");
if ("".equals(value)) {
value = null;
}
}
node.putPOJO("value", value);
}
}
String json = mapper.writeValueAsString(root);
//System.err.println("** JSON: "+json);
return Response.ok(json, MediaType.APPLICATION_JSON).build();
} catch (Exception e) {
e.printStackTrace();
throw new WebApplicationException(e, 500);
} finally {
try {
db.closeConnection();
} catch (SQLException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
}
}
}