package gov.nih.ncgc.bard.rest; import gov.nih.ncgc.bard.entity.Assay; import gov.nih.ncgc.bard.entity.BardLinkedEntity; import gov.nih.ncgc.bard.entity.Experiment; import gov.nih.ncgc.bard.entity.ExperimentData; import gov.nih.ncgc.bard.entity.Substance; import gov.nih.ncgc.bard.tools.Util; import gov.nih.ncgc.search.MoleculeService; import gov.nih.ncgc.util.MolRenderer; import gov.nih.ncgc.util.functional.Functional; import gov.nih.ncgc.util.functional.IApplyFunction; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import javax.imageio.ImageIO; import javax.ws.rs.Consumes; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.NotFoundException; 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 chemaxon.struc.Molecule; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; /** * 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("/substances") public class BARDSubstanceResource extends BARDResource<Substance> { public static final String DATE_FORMAT_NOW = "yyyy-MM-dd HH:mm:ss"; static final String VERSION = "1.0"; public Class<Substance> getEntityClass() { return Substance.class; } public String getResourceBase() { return BARDConstants.API_BASE + "/substances"; } @GET @Produces("text/plain") @Path("/_info") public String info() { StringBuilder msg = new StringBuilder("Returns substance information\n\nAvailable resources:\n"); List<String> paths = Util.getResourcePaths(this.getClass()); for (String path : paths) msg.append(path).append("\n"); return msg.toString(); } @GET @Path("/") public Response getResources(@QueryParam("filter") String filter, @QueryParam("expand") String expand, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) { Response response = null; if (skip == null) skip = -1; if (top == null) top = -1; boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; try { if (countRequested) { if (filter != null && filter.contains("[active]")) response = Response.ok(String.valueOf(db.getSubstanceActiveCount())).build(); else if (filter != null && (filter.contains("[test]") || filter.contains("[tested]")) ) response = Response.ok(String.valueOf(db.getSubstanceTestCount())).build(); else response = Response.ok(String.valueOf(db.getEntityCount(Substance.class))).build(); } else { 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 (filter != null) filterClause = "&filter=" + filter; String linkString = null; if (skip + top <= db.getEntityCount(Substance.class)) linkString = BARDConstants.API_BASE + "/substances?skip=" + (skip + top) + "&top=" + top + "&" + expandClause + filterClause; List<Substance> substances; if (filter != null && (filter.contains("[active]") || filter.contains("[tested]"))) { substances = db.searchForSubstances(filter, skip, top, false); } else substances = db.searchForEntity(filter, skip, top, Substance.class); if (expandEntries) { BardLinkedEntity linkedEntity = new BardLinkedEntity(substances, linkString); response = Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } else { List<String> links = new ArrayList<String>(); for (Substance a : substances) links.add(a.getResourcePath()); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); response = Response.ok(Util.toJson(linkedEntity), MediaType.APPLICATION_JSON).build(); } } db.closeConnection(); } catch (SQLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } return response; } @GET @Path("/{sid}/assays") public Response getAssaysForSubstance(@PathParam("sid") Long sid, String expand, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) throws SQLException, IOException { Response response; String linkString = null; List<Assay> p = db.getSubstanceAssays(sid, -1, -1); if (p == null) p = new ArrayList<Assay>(); if (countRequested) response = Response.ok(String.valueOf(p.size())).type(MediaType.TEXT_PLAIN).build(); if (p.size() > 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(expand)) expandClause = "expand=true"; if (skip + top <= p.size()) linkString = BARDConstants.API_BASE + "/substances/" + sid + "/assays?skip=" + (skip + top) + "&top=" + top + "&" + expandClause; } if (!expandEntries(expand)) { List<String> links = Functional.Apply(p, new IApplyFunction<Assay, String>() { public String eval(Assay assay) { return assay.getResourcePath(); } }); BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); response = Response.ok(Util.toJson(linkedEntity)).type(MediaType.APPLICATION_JSON).build(); } else { BardLinkedEntity linkedEntity = new BardLinkedEntity(p, linkString); response = Response.ok(Util.toJson(linkedEntity)).type(MediaType.APPLICATION_JSON).build(); } db.closeConnection(); return response; } private Response getCompoundResponse(String id, String type, List<MediaType> mediaTypes, boolean expand) throws SQLException, IOException { if (!type.equals("cid") && !type.equals("sid")) throw new WebApplicationException(400); Substance s = null; if (type.equals("cid")) { List<Long> sids = db.getSidsByCid(Long.parseLong(id)); if (!expand) { List<String> paths = new ArrayList<String>(); for (Long sid : sids) paths.add(BARDConstants.API_BASE + "/substances/" + sid); db.closeConnection(); return Response.ok(Util.toJson(paths), MediaType.APPLICATION_JSON).build(); } else { // TODO should be able to get multiple SIDs at one go List<Substance> slist = new ArrayList<Substance>(); for (Long sid : sids) { Substance sub = db.getSubstanceBySid(sid); if (sub != null) { slist.add(sub); } else { log("No substance found for sid " + sid); } } db.closeConnection(); return Response.ok(Util.toJson(slist), MediaType.APPLICATION_JSON).build(); } } else if (type.equals("sid")) s = db.getSubstanceBySid(Long.parseLong(id)); db.closeConnection(); if (s == null || s.getSid() == null) throw new WebApplicationException(404); Response response = null; if (mediaTypes.contains(BARDConstants.MIME_SMILES)) { // String smiles = c.getSmiles() + "\t" + id; // return Response.ok(smiles, BARDConstants.MIME_SMILES).build(); } else if (mediaTypes.contains(BARDConstants.MIME_SDF)) { // Molecule mol = MolImporter.importMol(c.getSmiles()); // mol.setProperty("cid", String.valueOf(c.getCid())); // mol.setProperty("probeId", c.getProbeId()); // mol.setProperty("url", c.getUrl()); // mol.setProperty("resourecePath", c.getResourcePath()); // String sdf = mol.exportToFormat("sdf"); // return Response.ok(sdf, BARDConstants.MIME_SDF).build(); } else { String json = s.toJson(); response = Response.ok(json, MediaType.APPLICATION_JSON).build(); } return response; } // return compound (via CID) for this SID @GET @Path("/{sid}") public Response getResources(@PathParam("sid") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) { try { Response response = getCompoundResponse(resourceId, "sid", headers.getAcceptableMediaTypes(), expand != null && expand.equals("true")); if (countRequested && response != null) return Response.ok("1", MediaType.TEXT_PLAIN).build(); else return response; } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } } // return list of SID's for this CID @GET @Path("/cid/{cid}") public Response getCompoundBySid(@PathParam("cid") String resourceId, @QueryParam("filter") String filter, @QueryParam("expand") String expand) { try { Response response = getCompoundResponse(resourceId, "cid", headers.getAcceptableMediaTypes(), expand != null && expand.equals("true")); if (countRequested && response != null) return Response.ok("1", MediaType.TEXT_PLAIN).build(); else return response; } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } } @POST @Path("/cid") @Consumes("application/x-www-form-urlencoded") public Response getCompoundByCids(@FormParam("cids") String cids, @QueryParam("filter") String filter, @QueryParam("expand") String expand) throws SQLException { if (cids == null) throw new WebApplicationException( new Exception("Must specify the cids form parameter as a comma separated list of CIDs"), 400); String[] toks = cids.split(","); Long[] lcids = new Long[toks.length]; for (int i = 0; i < toks.length; i++) lcids[i] = Long.parseLong(toks[i].trim()); try { Long[][] mapping = db.getSidsByCids(lcids); ObjectMapper mapper = new ObjectMapper(); ObjectNode node = mapper.createObjectNode(); ArrayNode anode = mapper.createArrayNode(); Long oldCid = mapping[0][0]; anode.add(mapping[0][1]); for (int i = 1; i < mapping.length; i++) { Long cid = mapping[i][0]; Long sid = mapping[i][1]; anode.add(sid); if (cid != oldCid) { node.put(oldCid.toString(), anode); anode = mapper.createArrayNode(); anode.add(sid); } oldCid = cid; } node.put(oldCid.toString(), anode); return Response.ok(mapper.writeValueAsString(node)).type(MediaType.APPLICATION_JSON_TYPE).build(); } catch (SQLException e) { throw new WebApplicationException(e, 500); } catch (JsonProcessingException e) { throw new WebApplicationException(e, 500); } catch (IOException e) { throw new WebApplicationException(e, 500); } finally { db.closeConnection(); } } @GET @Path("/{sid}/image") public Response getImage(@PathParam("sid") String resourceId, @QueryParam("s") Integer s, @QueryParam("c") String c, @QueryParam("a") String a) { try { MoleculeService molsrv = (MoleculeService) Util.getMoleculeService(); Molecule molecule = molsrv.getMol(resourceId); if (molecule == null) throw new NotFoundException("No molecule for CID = " + resourceId); MolRenderer renderer = new MolRenderer(); // size int size = 120; if (s != null && s >= 16 && s <= 512) size = s; // atom if (a != null) { for (String idx : a.split(",")) { try { int i = Integer.parseInt(idx); if (i > 0 && i <= molecule.getAtomCount()) { molecule.getAtom(i - 1).setAtomMap(1); } } catch (NumberFormatException ex) { throw new WebApplicationException(ex, 400); } } } if (c != null) { try { Color color = Color.decode(c); renderer.setBackground(color); } catch (NumberFormatException ex) { throw new WebApplicationException(ex, 400); } } BufferedImage img = renderer.createImage(molecule, size); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ImageIO.write(img, "png", baos); return Response.ok(baos.toByteArray()).type("image/png").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); } } // return alle xperiment data for this SID @GET @Path("/{sid}/exptdata") public Response getExperimentData(@PathParam("sid") 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; String linkString = null; if (skip == null) skip = -1; if (top == null) top = -1; try { 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"; linkString = BARDConstants.API_BASE + "/substances/" + resourceId + "/exptdata?skip=" + (skip + top) + "&top=" + top + "&" + expandClause; String json; if (!expandEntries) { List<String> edids = db.getSubstanceDataIds(Long.valueOf(resourceId), skip, top, filter); if (countRequested) json = String.valueOf(edids.size()); else { 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 { List<ExperimentData> data = db.getSubstanceData(Long.valueOf(resourceId), skip, top, filter); if (countRequested) json = String.valueOf(data.size()); else { BardLinkedEntity linkedEntity = new BardLinkedEntity(data, 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); } } @GET @Path("/{sid}/experiments") public Response getExperiments(@PathParam("sid") 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; String linkString = null; String json; if (skip == null) skip = -1; if (top == null) top = -1; try { 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"; linkString = BARDConstants.API_BASE + "/substances/" + resourceId + "/experiments?skip=" + (skip + top) + "&top=" + top + "&" + expandClause; if (!expandEntries) { List<Long> eids = db.getSubstanceExperimentIds(Long.valueOf(resourceId), skip, top); if (countRequested) json = String.valueOf(eids.size()); else { List<String> links = new ArrayList<String>(); for (Long eid : eids) { Experiment ed = new Experiment(); ed.setBardExptId(eid); links.add(ed.getResourcePath()); } BardLinkedEntity linkedEntity = new BardLinkedEntity(links, linkString); json = Util.toJson(linkedEntity); } } else { List<Experiment> data = db.getSubstanceExperiment(Long.valueOf(resourceId), skip, top); if (!countRequested) { BardLinkedEntity linkedEntity = new BardLinkedEntity(data, linkString); json = Util.toJson(linkedEntity); } else json = String.valueOf(data.size()); } 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); } } @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<Substance> substances = db.getSubstanceByETag(skip != null ? skip : -1, top != null ? top : -1, resourceId); String json = Util.toJson(substances); 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(); } } } }