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.capextract.CAPDictionary; import gov.nih.ncgc.bard.capextract.CAPDictionaryElement; import gov.nih.ncgc.bard.entity.Assay; import gov.nih.ncgc.bard.entity.BardLinkedEntity; import gov.nih.ncgc.bard.entity.Biology; import gov.nih.ncgc.bard.entity.Compound; import gov.nih.ncgc.bard.entity.Experiment; import gov.nih.ncgc.bard.entity.Project; import gov.nih.ncgc.bard.entity.ProjectStep; import gov.nih.ncgc.bard.entity.Publication; import gov.nih.ncgc.bard.search.Facet; import gov.nih.ncgc.bard.tools.IJsonConverter; import gov.nih.ncgc.bard.tools.ProjectListJsonConverter; import gov.nih.ncgc.bard.tools.Util; 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.MediaType; import javax.ws.rs.core.Response; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.math.BigInteger; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; 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("/projects") public class BARDProjectResource extends BARDResource<Project> { public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss"; static final String VERSION = "1.0"; public Class<Project> getEntityClass () { return Project.class; } public String getResourceBase () { return BARDConstants.API_BASE+"/projects"; } @GET @Produces("text/plain") @Path("/_info") public String info() { StringBuilder msg = new StringBuilder("Returns project information\n\nAvailable resources:\n"); List<String> paths = Util.getResourcePaths(this.getClass()); for (String path : paths) msg.append(path).append("\n"); msg.append("/projects/" + 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; Response response = null; try { String linkString = null; if (filter == null) { // just list all projects if (countRequested) return Response.ok(String.valueOf(db.getEntityCount(Project.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(Project.class)) linkString = BARDConstants.API_BASE + "/projects?skip=" + (skip + top) + "&top=" + top + "&" + expandClause; } List<Project> projects = db.searchForEntity(filter, skip, top, Project.class); db.closeConnection(); if (countRequested) return Response.ok(String.valueOf(projects.size()), MediaType.TEXT_PLAIN).build(); if (expandEntries) { BardLinkedEntity linkedEntity = new BardLinkedEntity(projects, linkString); IJsonConverter<BardLinkedEntity> jsonConverter = new ProjectListJsonConverter(); return Response.ok(Util.toJson(linkedEntity, jsonConverter), MediaType.APPLICATION_JSON).build(); } else { List<String> links = new ArrayList<String>(); for (Project project : projects) links.add(project.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); } catch (Exception e) { throw new WebApplicationException(e, 500); } } @GET @Path("/{id}") public Response getResources(@PathParam("id") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) { try { Project p = db.getProject(Long.valueOf(resourceId)); if (p == null) throw new WebApplicationException(404); String json; if (countRequested) json = Util.toJson("1"); else { json = Util.toJson(p); if (expandEntries(expand)) { // need to update publication, experiment and assay entries List<Assay> assays = new ArrayList<Assay>(); for (Long aid : p.getAids()) assays.add(db.getAssayByAid(aid)); List<Experiment> expts = new ArrayList<Experiment>(); for (Long eid : p.getEids()) expts.add(db.getExperimentByExptId(eid)); List<Publication> pubs = new ArrayList<Publication>(); for (Long pmid : p.getPublications()) pubs.add(db.getPublicationByPmid(pmid)); ObjectMapper mapper = new ObjectMapper(); ArrayNode an = mapper.createArrayNode(); for (Assay assay : assays) { an.add(mapper.valueToTree(assay)); } ArrayNode en = mapper.createArrayNode(); for (Experiment expt : expts) { en.add(mapper.valueToTree(expt)); } ArrayNode pn = mapper.createArrayNode(); for (Publication pub : pubs) { pn.add(mapper.valueToTree(pub)); } JsonNode tree = mapper.valueToTree(p); ((ObjectNode)tree).put("eids", en); ((ObjectNode)tree).put("aids", an); ((ObjectNode)tree).put("publications", pn); Writer writer = new StringWriter(); JsonFactory fac = new JsonFactory(); JsonGenerator jsg = fac.createJsonGenerator(writer); mapper.writeTree(jsg, tree); json = writer.toString(); } } 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); } } @POST @Path("/") @Consumes("application/x-www-form-urlencoded") public Response getResources(@FormParam("ids") String pids, @QueryParam("expand") String expand) { if (pids == null) throw new WebApplicationException(new Exception("POST request must specify the pids form parameter, which should be a comma separated string of project IDs"), 400); try { // we'll asssume an ID list if we're being called via POST String[] s = pids.split(","); Long[] ids = new Long[s.length]; for (int i = 0; i < s.length; i++) ids[i] = Long.parseLong(s[i].trim()); List<Project> p = db.getProjects(ids); if (countRequested) return Response.ok(String.valueOf(p.size()), MediaType.TEXT_PLAIN).build(); db.closeConnection(); String json; if (expand == null || expand.toLowerCase().equals("false")) { List<String> links = new ArrayList<String>(); for (Project ap : p) if (ap != null) links.add(ap.getResourcePath()); json = Util.toJson(links); } else json = Util.toJson(p); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } } // TODO only list targets associated with the summary assay, not the member assays. Correct? @GET @Path("/{id}/targets") public Response getTargetsForProject(@PathParam("id") String resourceId, @QueryParam("filter") String filter, @QueryParam("search") String search, @QueryParam("expand") String expand) { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; try { Project project = db.getProject(Long.valueOf(resourceId)); List<Biology> targets = project.getTargets(); String json; if (countRequested) json = Util.toJson(targets.size()); else if (expandEntries) json = Util.toJson(targets); else { List<String> links = new ArrayList<String>(); for (Biology pt : targets) links.add(pt.getResourcePath()); json = Util.toJson(links); } 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); } } @GET @Path("/{id}/experiments") public Response getExperimentsForProject (@PathParam("id") String resourceId, @QueryParam("filter") String filter, @QueryParam("search") String search, @QueryParam("expand") String expand) { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; try { Project p = db.getProject(Long.valueOf(resourceId)); List<Experiment> e = new ArrayList<Experiment>(); for (Long eid : p.getEids()) e.add(db.getExperimentByExptId(eid)); String json; if (countRequested) json = Util.toJson(e.size()); else if (expandEntries) { json = Util.toJson(e); } else { List<String> links = new ArrayList<String>(); for (Experiment experiment : e) links.add(experiment.getResourcePath()); json = Util.toJson(links); } 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); } } @GET @Path("/{id}/assays") public Response getAssaysForProject (@PathParam("id") String resourceId, @QueryParam("filter") String filter, @QueryParam("search") String search, @QueryParam("expand") String expand) { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; try { Project p = db.getProject(Long.valueOf(resourceId)); List<Assay> e = new ArrayList<Assay>(); for (Long aid : p.getAids()) e.add(db.getAssayByAid(aid)); String json; if (countRequested) json = Util.toJson(e.size()); else if (expandEntries) { json = Util.toJson(e); } else { List<String> links = new ArrayList<String>(); for (Assay assay : e) links.add(assay.getResourcePath()); json = Util.toJson(links); } 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 compounds for a project. * * @param resourceId * @param filter * @param search * @param expand * @return String representation of compounds. Format is specified via Accepts: header and can be * chemical/x-daylight-smiles or chemical/x-mdl-sdfile for SMILES or SDF formats. */ @GET @Path("/{id}/probes") public Response getProbesForProject(@PathParam("id") String resourceId, @QueryParam("filter") String filter, @QueryParam("search") String search, @QueryParam("expand") String expand) { List<MediaType> types = headers.getAcceptableMediaTypes(); Response response = null; try { List<Long> probes = db.getProbeCidsForProject(Long.valueOf(resourceId)); if (countRequested) response = Response.ok(Util.toJson(probes.size()), MediaType.APPLICATION_JSON).build(); else if (types.contains(BARDConstants.MIME_SMILES)) { List<String> smiles = new ArrayList<String>(); for (Long probe : probes) { List<Compound> c = db.getCompoundsByCid(probes.toArray(new Long[]{})); for (Compound ac : c) smiles.add(ac.getSmiles()); } response = Response.ok(Util.join(smiles, "\n"), BARDConstants.MIME_SMILES).build(); } else if (types.contains(BARDConstants.MIME_SDF)) { } else { List<String> links = new ArrayList<String>(); for (Long id : probes) links.add(BARDConstants.API_BASE + "/compounds/" + id); if (expandEntries(expand)) { List<Compound> cmpds = db.getCompoundsByCid(probes.toArray(new Long[]{})); response = Response.ok(Util.toJson(cmpds), MediaType.APPLICATION_JSON).build(); } else { response = Response.ok(Util.toJson(links), 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 @Path("/{pid}/annotations") public Response getAnnotations(@PathParam("pid") Long resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) throws ClassNotFoundException, IOException, SQLException { List<CAPAnnotation> a; CAPDictionary dict = db.getCAPDictionary(); try { a = db.getProjectAnnotations(resourceId); if (a == null) throw new WebApplicationException(404); // lets group these annotations and construct our JSON response CAPDictionaryElement node; Map<Integer, List<CAPAnnotation>> contexts = new HashMap<Integer, List<CAPAnnotation>>(); for (CAPAnnotation anno : a) { Integer id = anno.id; if (id == null) id = -1; // corresponds to dynamically generated annotations (from non-CAP sources) // go from dict key to label if (anno.key != null && Util.isNumber(anno.key)) { node = dict.getNode(new BigInteger(anno.key)); anno.key = node != null ? node.getLabel() : anno.key; } if (anno.value != null && Util.isNumber(anno.value)) { node = dict.getNode(new BigInteger(anno.value)); anno.value = node != null ? node.getLabel() : anno.value; } if (contexts.containsKey(id)) { List<CAPAnnotation> la = contexts.get(id); la.add(anno); contexts.put(id, la); } else { List<CAPAnnotation> la = new ArrayList<CAPAnnotation>(); la.add(anno); contexts.put(id, la); } } ObjectMapper mapper = new ObjectMapper(); ArrayNode docNode = mapper.createArrayNode(); ArrayNode contextNode = mapper.createArrayNode(); ArrayNode measureNode = mapper.createArrayNode(); ArrayNode miscNode = mapper.createArrayNode(); for (Integer contextId : contexts.keySet()) { List<CAPAnnotation> comps = contexts.get(contextId); Collections.sort(comps, new Comparator<CAPAnnotation>() { @Override public int compare(CAPAnnotation o1, CAPAnnotation o2) { if (o1.displayOrder == o2.displayOrder) return 0; return o1.displayOrder < o2.displayOrder ? -1 : 1; } }); JsonNode arrayNode = mapper.valueToTree(comps); ObjectNode n = mapper.createObjectNode(); n.put("id", comps.get(0).id); n.put("name", comps.get(0).contextRef); n.put("comps", arrayNode); if (comps.get(0).source.equals("cap-doc")) docNode.add(n); else if (comps.get(0).source.equals("cap-context")) contextNode.add(n); else if (comps.get(0).source.equals("cap-measure")) measureNode.add(n); else { for (CAPAnnotation misca : comps) miscNode.add(mapper.valueToTree(misca)); } } ObjectNode topLevel = mapper.createObjectNode(); topLevel.put("contexts", contextNode); topLevel.put("measures", measureNode); topLevel.put("docs", docNode); topLevel.put("misc", miscNode); 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(); } } } @Override @GET @Path("/etag/{etag}/facets") public Response getFacets(@PathParam("etag") String resourceId) { try { List<Facet> facets = db.getProjectFacets(resourceId); return Response.ok(Util.toJson(facets), MediaType.APPLICATION_JSON).build(); } catch (Exception ex) { throw new WebApplicationException(ex, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } @GET @Path("/{pid}/summary") public Response getSummary(@PathParam("pid") Long projectId) { try { Map<String, Object> summary = db.getProjectSumary(projectId); if (summary == null) throw new WebApplicationException(404); return Response.ok(Util.toJson(summary), 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. } } } @GET @Path("/{pid}/steps") public Response getProjectSteps(@PathParam("pid") Long projectId, @QueryParam("expand") String expand) throws SQLException, IOException { List<ProjectStep> steps = db.getProjectStepsByProjectId(projectId); if (steps.size() == 0) { db.closeConnection(); throw new WebApplicationException(404); } String json; if (expandEntries(expand)) { ObjectMapper mapper = new ObjectMapper(); ArrayNode anode = mapper.createArrayNode(); for (ProjectStep step : steps) { ObjectNode node = mapper.valueToTree(step); Experiment e = db.getExperimentByExptId(step.getNextBardExpt()); node.put("nextBardExpt", mapper.valueToTree(e)); e = db.getExperimentByExptId(step.getPrevBardExpt()); node.put("prevBardExpt", mapper.valueToTree(e)); anode.add(node); } json = mapper.writeValueAsString(anode); } else { json = Util.toJson(steps); } db.closeConnection(); return Response.ok(json).type(MediaType.APPLICATION_JSON_TYPE).build(); } @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<Project> projects = db.getProjectsByETag (skip != null ? skip : -1, top != null ? top : -1, resourceId); String json; if (expand == null || expand.toLowerCase().equals("false")) { List<String> links = new ArrayList<String>(); for (Project ap : projects) if (ap != null) links.add(ap.getResourcePath()); json = Util.toJson(links); } else json = Util.toJson(projects); return Response.ok(json, MediaType.APPLICATION_JSON).build(); } catch (Exception e) { throw new WebApplicationException(Response.status(500).entity(e.getMessage()).build()); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } }