package gov.nih.ncgc.bard.rest; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonNode; 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.capextract.CAPAnnotation; import gov.nih.ncgc.bard.entity.Assay; import gov.nih.ncgc.bard.entity.BardLinkedEntity; import gov.nih.ncgc.bard.entity.Compound; import gov.nih.ncgc.bard.entity.Experiment; import gov.nih.ncgc.bard.entity.ExperimentData; import gov.nih.ncgc.bard.entity.ExperimentResultType; import gov.nih.ncgc.bard.entity.Project; import gov.nih.ncgc.bard.entity.Substance; import gov.nih.ncgc.bard.tools.AnnotationUtils; import gov.nih.ncgc.bard.tools.DBUtils; import gov.nih.ncgc.bard.tools.Util; import gov.nih.ncgc.util.functional.Functional; import gov.nih.ncgc.util.functional.IApplyFunction; 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.io.StringWriter; import java.io.Writer; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 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("/experiments") public class BARDExperimentResource extends BARDResource<Experiment> { 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; public Class<Experiment> getEntityClass () { return Experiment.class; } public String getResourceBase () { return BARDConstants.API_BASE+"/experiments"; } @GET @Produces("text/plain") @Path("/_info") public String info() { StringBuilder msg = new StringBuilder("Returns experiment information\n\nAvailable resources:\n"); List<String> paths = Util.getResourcePaths(this.getClass()); for (String path : paths) msg.append(path).append("\n"); msg.append("/experiments/" + BARDConstants.API_EXTRA_PARAM_SPEC + "\n"); return msg.toString(); } @GET 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; if (skip == null) skip = -1; if (top == null) top = -1; try { String linkString = null; if (filter == null) { if (countRequested) return Response.ok(String.valueOf(db.getEntityCount(Experiment.class)), MediaType.TEXT_PLAIN).build(); if ((top == -1)) { // top was not specified, so we start from the beginning top = getDefaultEntityCount(); } if (skip == -1) skip = 0; String expandClause = "expand=false"; if (expandEntries) expandClause = "expand=true"; if (skip + top <= db.getEntityCount(Experiment.class)) linkString = BARDConstants.API_BASE + "/experiments?skip=" + (skip + top) + "&top=" + top + "&" + expandClause; } List<Experiment> experiments = db.searchForEntity(filter, skip, top, Experiment.class); db.closeConnection(); if (countRequested) return Response.ok(String.valueOf(experiments.size()), MediaType.TEXT_PLAIN).build(); if (expandEntries) { BardLinkedEntity linkedEntity = new BardLinkedEntity(experiments, linkString); return Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } else { List<String> links = new ArrayList<String>(); for (Experiment experiment : experiments) links.add(experiment.getResourcePath()); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); return Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } finally { try { db.closeConnection(); } catch (SQLException e) { } } } @POST @Path("/") @Consumes("application/x-www-form-urlencoded") public Response getResources(@FormParam("ids") String eids, @QueryParam("expand") String expand) throws SQLException { try { if (eids == null) throw new BadRequestException("POST request must specify the ids form parameter, which should be a comma separated string of experiment IDs"); List<Experiment> experiments = new ArrayList<Experiment>(); for (String s : eids.split("[,;\\s]")) { try { Experiment e = db.getExperimentByExptId (Long.parseLong(s)); experiments.add(e); } catch (NumberFormatException ex) { // ignore bogus } } return Response.ok(Util.toJson(experiments), MediaType.APPLICATION_JSON).build(); } catch (Exception ex) { throw new WebApplicationException (ex, 500); } finally { db.closeConnection(); } } @GET @Path("/{eid}") public Response getResources(@PathParam("eid") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) { Experiment experiment; try { experiment = db.getExperimentByExptId(Long.valueOf(resourceId)); if (experiment == null || experiment.getBardExptId() == null) throw new WebApplicationException(404); String json = Util.toJson(experiment); if (expand != null && expand.toLowerCase().trim().equals("true")) { // expand experiment and project entries json = getExpandedJson(experiment, Long.parseLong(resourceId), db).toString(); } return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (SQLException e) { e.printStackTrace(); throw new WebApplicationException(e, 500); } catch (IOException 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. } } } JsonNode getExpandedJson(Experiment e, Long eid, DBUtils db) throws SQLException { ObjectMapper mapper = new ObjectMapper(); JsonNode t = mapper.valueToTree(e); List<Assay> assays = db.getAssaysByExperimentId(eid); ArrayNode an = mapper.createArrayNode(); for (Assay a : assays) { ObjectNode on = mapper.valueToTree(a); an.add(on); } ((ObjectNode) t).put("assayId", an); // List<Project> projs = db.getProjectByAssayId(eid); // an = mapper.createArrayNode(); // for (Project e : projs) { // ObjectNode on = mapper.valueToTree(e); // an.add(on); // } // ((ObjectNode) t).put("projects", an); return t; } @GET @Path("/{eid}/projects") public Response getProjects(@PathParam("eid") Long eid, @QueryParam("filter") String filter, @QueryParam("expand") String expand) throws SQLException, IOException { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; List<Project> projects = db.getProjectByExperimentId(eid); if (!expandEntries) { List<String> links = Functional.Apply(projects, new IApplyFunction<Project, String>() { public String eval(Project project) { return project.getResourcePath(); } }); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, null); return Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } else { BardLinkedEntity linkedEntity = new BardLinkedEntity(projects, null); return Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } } // TODO right now, we don't support filtering on compounds @GET @Path("/{eid}/compounds") public Response getExperimentCompounds(@PathParam("eid") String resourceId, @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; List<MediaType> types = headers.getAcceptableMediaTypes(); String linkString = null; if (skip == null) skip = -1; if (top == null) top = -1; boolean filterActives = false; if (filter != null && filter.contains("[active]")) filterActives = true; try { if (countRequested) { int n = db.getExperimentCidCount(Long.valueOf(resourceId), filterActives); db.closeConnection(); return Response.ok(String.valueOf(n), MediaType.TEXT_PLAIN).build(); } Experiment experiment = db.getExperimentByExptId(Long.valueOf(resourceId)); // set up skip and top params if (experiment.getCompounds() > getDefaultEntityCount()) { if ((top == -1)) { // top was not specified, so we start from the beginning top = getDefaultEntityCount(); } if (skip == -1) skip = 0; String expandClause = "expand=false"; if (expandEntries) expandClause = "expand=true"; String filterClause = ""; if (filterActives) filterClause = "&filter=[active]"; if (skip + top <= experiment.getCompounds()) linkString = BARDConstants.API_BASE + "/experiments/" + resourceId + "/compounds?skip=" + (skip + top) + "&top=" + top + "&" + expandClause+filterClause; } if (types.contains(BARDConstants.MIME_SMILES)) { } else if (types.contains(BARDConstants.MIME_SDF)) { } else { // JSON String json; if (!expandEntries) { List<Long> cids = db.getExperimentCids(Long.valueOf(resourceId), skip, top, filterActives); List<String> links = new ArrayList<String>(); for (Long cid : cids) links.add((new Compound(cid, null, null)).getResourcePath()); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); json = Util.toJson(linkedEntity); } else { List<Compound> compounds = db.getExperimentCompounds(Long.valueOf(resourceId), skip, top, filterActives); BardLinkedEntity linkedEntity = new BardLinkedEntity(compounds, linkString); json = Util.toJson(linkedEntity); } db.closeConnection(); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } return null; } @GET @Path("/{eid}/substances") public Response getExperimentSubstances(@PathParam("eid") String resourceId, @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; List<MediaType> types = headers.getAcceptableMediaTypes(); String linkString = null; if (skip == null) skip = -1; if (top == null) top = -1; boolean filterActives = false; if (filter != null && filter.contains("[active]")) filterActives = true; try { if (countRequested) { int n = db.getExperimentSidCount(Long.valueOf(resourceId), filterActives); db.closeConnection(); return Response.ok(String.valueOf(n), MediaType.TEXT_PLAIN).build(); } Experiment experiemnt = db.getExperimentByExptId(Long.valueOf(resourceId)); // set up skip and top params if (experiemnt.getSubstances() > getDefaultEntityCount()) { if ((top == -1)) { // top was not specified, so we start from the beginning top = getDefaultEntityCount(); } if (skip == -1) skip = 0; String expandClause = "expand=false"; if (expandEntries) expandClause = "expand=true"; String filterClause = ""; if (filterActives) filterClause = "&filter=[active]"; if (skip + top <= experiemnt.getSubstances()) linkString = BARDConstants.API_BASE + "/experiments/" + resourceId + "/substances?skip=" + (skip + top) + "&top=" + top + "&" + expandClause + filterClause; } if (types.contains(BARDConstants.MIME_SMILES)) { } else if (types.contains(BARDConstants.MIME_SDF)) { } else { // JSON String json; if (!expandEntries) { List<Long> sids = db.getExperimentSids(Long.valueOf(resourceId), skip, top, filterActives); List<String> links = new ArrayList<String>(); for (Long sid : sids) links.add((new Substance(sid, null)).getResourcePath()); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); json = Util.toJson(linkedEntity); } else { List<Compound> compounds = db.getExperimentSubstances(Long.valueOf(resourceId), skip, top, filterActives); BardLinkedEntity linkedEntity = new BardLinkedEntity(compounds, linkString); json = Util.toJson(linkedEntity); } db.closeConnection(); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } } catch (SQLException e) { logger.warning(e.toString()); throw new WebApplicationException(e, 500); } catch (IOException e) { logger.warning(e.toString()); throw new WebApplicationException(e, 500); } return null; } @GET @Path("/{eid}/metadata") public Response getExperimentData(@PathParam("eid") String resourceId) throws SQLException { try { String json = db.getExperimentMetadataByExptId (Long.parseLong(resourceId)); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (Exception e) { throw new WebApplicationException(e, 500); } finally { db.closeConnection(); } } @GET @Path("/{eid}/exptdata") public Response getExperimentData(@PathParam("eid") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) throws SQLException { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; String linkString = null; if (skip == null) skip = -1; if (top == null) top = -1; try { Experiment experiemnt = db.getExperimentByExptId(Long.valueOf(resourceId)); // set up skip and top params if (experiemnt.getSubstances() > BARDConstants.MAX_DATA_COUNT) { if ((top == -1)) { // top was not specified, so we start from the beginning top = BARDConstants.MAX_DATA_COUNT; } if (skip == -1) skip = 0; String expandClause = "expand=false"; if (expandEntries) expandClause = "expand=true"; String filterClause = ""; if (filter != null) filterClause = "filter=" + filter; if (skip + top <= experiemnt.getSubstances()) linkString = BARDConstants.API_BASE + "/experiments/" + resourceId + "/exptdata?skip=" + (skip + top) + "&top=" + top + "&" + expandClause + "&" + filterClause; } String json; if (!expandEntries) { List<String> edids = db.getExperimentDataIds(Long.valueOf(resourceId), skip, top, filter); List<String> links = new ArrayList<String>(); for (String edid : edids) { ExperimentData ed = new ExperimentData(); ed.setExptDataId(edid); links.add(ed.getResourcePath()); } BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); json = Util.toJson(linkedEntity); } else { long start = System.currentTimeMillis(); List<ExperimentData> data = db.getExperimentData(Long.valueOf(resourceId), skip, top, filter); long end = System.currentTimeMillis(); double dbQueryTime = (end - start) / 1000.0; BardLinkedEntity linkedEntity = new BardLinkedEntity(data, linkString); start = System.currentTimeMillis(); json = Util.toJson(linkedEntity); end = System.currentTimeMillis(); double jsonTime = (end - start) / 1000.0; logger.info("Time to retrieve " + data.size() + " expanded entries for expt " + resourceId + " used " + dbQueryTime + "s for DB query and " + jsonTime + "s for JSON generation"); } db.closeConnection(); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (SQLException e) { db.closeConnection(); throw new WebApplicationException(Response.status(500).entity(e.toString()).build()); } catch (IOException e) { db.closeConnection(); throw new WebApplicationException(Response.status(500).entity(e.toString()).build()); } } /* @POST @Path("/{eid}/exptdata") public Response getExperimentData(@PathParam("eid") String resourceId, @FormParam("sids") String sids) throws SQLException { if (sids == null) { throw new BadRequestException (getRequestURI()+": No \"sids\" form parameter specified " +"for POST!"); } try { Experiment experiemnt = db.getExperimentByExptId (Long.valueOf(resourceId)); // not done! String json = Util.toJson(experiment); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (Exception ex) { throw new BadRequestException (ex.getMessage()); } finally { db.closeConnection(); } } */ @GET @Path("/{eid}/etag/{etag}/exptdata") public Response getExperimentDataETag(@PathParam("eid") Long eid, @PathParam("etag") String etag, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) throws SQLException, IOException { try { List<ExperimentData> data = db.getExperimentDataByETag (skip != null ? skip : -1, top != null ? top : -1, eid, etag); return Response.ok(Util.toJson(data), MediaType.APPLICATION_JSON).build(); } finally { db.closeConnection(); } } @Override @GET @Path("/etag/{etag}") public Response getEntitiesByETag(@PathParam("etag") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) { try { List<Experiment> expts = db.getExperimentsByETag(skip != null ? skip : -1, top != null ? top : -1, resourceId); String json = Util.toJson(expts); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (Exception e) { throw new WebApplicationException(e, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } @GET @Path("/{eid}/summary") public Response getSummary(@PathParam("eid") Long eid) throws IOException, SQLException { long start = System.currentTimeMillis(); Map<String, Object> s = new HashMap<String, Object>(); Experiment e = db.getExperimentByExptId(eid); if (e == null || e.getBardExptId() == null) throw new WebApplicationException(404); int nsub = e.getSubstances(); s.put("compounds.tested", e.getCompounds()); s.put("substances.tested", nsub); int nhit = 0; Map<String, Integer> colanno = new HashMap<String, Integer>(); List<ExperimentData> data = db.getActiveExperimentData(eid, -1, -1); for (ExperimentData ed : data) { // should we check whether it is a confirmatory screen? // if (ed.getOutcome() == 2 && e.getType() == 2) { // nhit++; // } Map<String, String[]> annos = db.getCompoundAnnotations(ed.getCid()); String[] keys = annos.get("anno_key"); String[] vals = annos.get("anno_val"); for (int i = 0; i < keys.length; i++) { if (keys[i].equals("COLLECTION")) { String val = vals[i].trim().split("\\|")[0]; if (colanno.containsKey(val)) colanno.put(val, colanno.get(val) + 1); else colanno.put(val, 1); } } } s.put("COLLECTION", colanno); s.put("nhit", data.size()); double duration = (System.currentTimeMillis() - start) / 1000.0; logger.info("Time to generate summary was " + duration + "s"); return Response.ok(Util.toJson(s), MediaType.APPLICATION_JSON).build(); } @GET @Path("/{eid}/resulttypes") @Produces("application/json") public Response getExperimentResultTypes(@PathParam("eid") Long eid, @QueryParam("expand") String expand, @QueryParam("collapse") Integer collapse) { try { List<ExperimentResultType> rtypes = db.getExperimentResultTypes(eid, collapse); String json = null; if (expandEntries(expand)) json = Util.toJson(rtypes); else { List<String> rtypeNames = new ArrayList<String>(); for (ExperimentResultType rtype : rtypes) rtypeNames.add(rtype.getName()); json = Util.toJson(rtypeNames); } return Response.ok(json).type(MediaType.APPLICATION_JSON).build(); } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } } @GET @Produces("application/json") @Path("/{eid}/annotations") public Response getAnnotations(@PathParam("eid") Long eid, @QueryParam("filter") String filter, @QueryParam("expand") String expand) throws ClassNotFoundException, IOException, SQLException { List<CAPAnnotation> a; try { a = db.getExperimentAnnotations(eid); if (a == null) throw new WebApplicationException(404); JsonNode topLevel = AnnotationUtils.getAnnotationJson(a); ObjectMapper mapper = new ObjectMapper(); Writer writer = new StringWriter(); JsonFactory fac = new JsonFactory(); JsonGenerator jsg = fac.createJsonGenerator(writer); mapper.writeTree(jsg, topLevel); String json = writer.toString(); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (SQLException e) { throw new WebApplicationException(Response.status(500).entity(e.getMessage()).build()); } catch (IOException e) { throw new WebApplicationException(Response.status(500).entity(e.getMessage()).build()); } finally { try { db.closeConnection(); } catch (SQLException ex) { ex.printStackTrace(); } } } }