package gov.nih.ncgc.bard.rest; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import gov.nih.ncgc.bard.entity.BardEntity; import gov.nih.ncgc.bard.tools.DBUtils; import gov.nih.ncgc.bard.tools.JsonUtil; import gov.nih.ncgc.bard.tools.Util; import javax.annotation.PostConstruct; import javax.servlet.ServletConfig; 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.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.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.EntityTag; 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.List; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; /** * A base class for all REST resource class. * <p/> * Provides some useful utility methods and fields. * * @author Rajarshi Guha */ public abstract class BARDResource<T extends BardEntity> implements IBARDResource { protected static DBUtils db; static final Logger logger = Logger.getLogger(BARDResource.class.getName()); @Context ServletConfig servletConfig; @Context ServletContext servletContext; @Context HttpServletRequest httpServletRequest; @Context protected HttpHeaders headers; /** * <code>true</code> if the request specified a count of entities rather than the entities themselves. */ protected boolean countRequested; protected String jsonpMethodName = null; protected List<EntityTag> etagsRequested = new ArrayList<EntityTag>(); protected BARDResource() { } protected int getDefaultEntityCount() { String s = servletContext.getInitParameter("default-entity-count"); int n = BARDConstants.MAX_COMPOUND_COUNT; if (s != null) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return n; } } else return BARDConstants.MAX_COMPOUND_COUNT; } public static void setDb(DBUtils db) { BARDResource.db = db; } @PostConstruct protected void postConstruct() { countRequested = Util.countRequested(headers); List<String> etags = headers.getRequestHeader(HttpHeaders.IF_MATCH); if (etags != null) { System.err.print("## If-Match: "); for (String entry : etags) { for (String e : entry.split(",")) { EntityTag t = EntityTag.valueOf(e.trim()); System.err.print(" " + t.getValue()); etagsRequested.add(t); } } System.err.println(" " + etagsRequested.size()); } System.err.println("## Request URI: " + getRequestURI()); } protected List<EntityTag> getETagsRequested() { return etagsRequested; } protected boolean expandEntries(String expand) { boolean expandEntries = false; if (expand != null && (expand.toLowerCase().equals("true") || expand.toLowerCase().equals("yes"))) expandEntries = true; return expandEntries; } protected ServletContext getServletContext() { return servletContext; } protected String getRequestURI() { String query = httpServletRequest.getQueryString(); return (httpServletRequest.getMethod() + ":" + httpServletRequest.getRequestURI() + (query != null ? ("?" + query) : "")); } public abstract Class<T> getEntityClass(); public abstract String getResourceBase(); /* * ETag common resources */ @GET @Path("/etag") public Response getETags(@QueryParam("expand") String expand, @QueryParam("skip") Integer skip, @QueryParam("top") Integer top) { try { Response response = null; if (top == null) { top = BARDConstants.MAX_DATA_COUNT; } if (skip == null) { skip = 0; } List<String> etags = db.getETagsForEntity (skip, top, null /* Principal */, getEntityClass()); String linkString = null; if (etags.size() == top) { // there are more linkString = getResourceBase() + "/etag?skip=" + (skip + top) + "&top=" + top + "&expand=" + expand; } if (expandEntries(expand)) { List<Map> entities = new ArrayList<Map>(); for (String e : etags) { Map et = db.getETagInfo(e); entities.add(et); } Map map = new TreeMap(); map.put("collection", entities); map.put("link", linkString); response = Response.ok(Util.toJson(map), MediaType.APPLICATION_JSON).build(); } else { Map res = new TreeMap(); res.put("collection", etags); res.put("link", linkString); response = Response.ok(Util.toJson(res), MediaType.APPLICATION_JSON).build(); } return response; } catch (Exception ex) { throw new WebApplicationException(ex, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { } } } @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) { throw new WebApplicationException (new UnsupportedOperationException(), 501); } @POST @Path("/etag") @Consumes("application/x-www-form-urlencoded") public Response createETag(@FormParam("name") String name, @FormParam("url") String url, @FormParam("ids") String ids, @FormParam("etagids") String etagids) { try { if (name == null) { throw new IllegalArgumentException ("No \"name\" specified!"); } EntityTag etag = new EntityTag (db.newETag(name, url, getEntityClass().getName())); if (ids != null) { List<Long> list = new ArrayList<Long>(); for (String id : ids.split("[,;\\s]")) { try { list.add(Long.parseLong(id)); } catch (NumberFormatException ex) { } } int cnt = db.putETag (etag.getValue(), list.toArray(new Long[0])); log("** New ETag: " + etag.getValue() + " \"" + name + "\" " + cnt); } else { log("** New ETag: " + etag.getValue() + " \"" + name + "\""); } if (etagids != null) { for (String anEtag : etagids.split(",")) { db.createETagLinks(anEtag.trim(), etag.getValue()); } } return Response.ok().tag(etag).build(); } catch (Exception ex) { throw new WebApplicationException(ex, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } @PUT @Path("/etag/{etag}") @Consumes("application/x-www-form-urlencoded") public Response putETag(@PathParam("etag") String etag, @FormParam("name") String name, @FormParam("ids") String ids, @FormParam("etagids") String etagids) { try { if (ids == null) { throw new IllegalArgumentException ("No \"ids\" param specified!"); } // check to make sure the correct type Map info = db.getETagInfo(etag); if (!getEntityClass().getName().equals(info.get("type"))) { throw new WebApplicationException (new IllegalArgumentException ("ETag " + etag + " is not of type " + getEntityClass()), 500); } List<Long> list = new ArrayList<Long>(); for (String id : ids.split("[,;\\s]")) { try { list.add(Long.parseLong(id)); } catch (NumberFormatException ex) { } } int cnt = db.putETag(etag, name, list.toArray(new Long[0])); log("** put ETag: " + etag + " " + cnt); if (etagids != null) { for (String anEtag : etagids.split(",")) { db.createETagLinks(anEtag.trim(), etag); } } return Response.ok(String.valueOf(cnt), "text/plain") .tag(etag).build(); } catch (Exception ex) { throw new WebApplicationException(ex, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } @GET @Path("/etag/{etag}/_info") public Response getETagInfo(@PathParam("etag") String resourceId) { try { Map info = db.getETagInfo(resourceId); return Response.ok(Util.toJson(info), MediaType.APPLICATION_JSON).build(); } catch (Exception ex) { throw new WebApplicationException(ex, 500); } finally { try { db.closeConnection(); } catch (Exception ex) { ex.printStackTrace(); } } } @GET @Path("/etag/{etag}/facets") public Response getFacets(@PathParam("etag") String resourceId) { throw new WebApplicationException (new UnsupportedOperationException(), 500); } protected void log(String mesg) { //servletContext.log(mesg); logger.info(mesg); } protected void log(String mesg, Throwable t) { //servletContext.log(mesg, t); logger.log(Level.SEVERE, mesg, t); } protected void warning(String mesg) { logger.warning(mesg); } @GET @Path("/_schema") @Produces(MediaType.APPLICATION_JSON) public Response getSchema() { try { ObjectMapper mapper = new ObjectMapper(); JsonNode schemaNode = JsonUtil.getJsonSchema(getEntityClass()); return Response.ok(mapper.writeValueAsString(schemaNode)).type(MediaType.APPLICATION_JSON_TYPE).build(); } catch (JsonMappingException e) { e.printStackTrace(); throw new WebApplicationException(500); } catch (IOException e) { e.printStackTrace(); throw new WebApplicationException(500); } } @GET @Path("/recent/{n}") @Produces(MediaType.APPLICATION_JSON) public Response getRecent(@PathParam("n") Integer n, @QueryParam("expand") String expand) { try { List<T> entities = db.getRecentEntities(getEntityClass(), n); if (expandEntries(expand)) return Response.ok(Util.toJson(entities)).type(MediaType.APPLICATION_JSON_TYPE).build(); else { List<String> ids = new ArrayList<String>(); for (T entity : entities) ids.add(entity.getResourcePath()); return Response.ok(Util.toJson(ids)).type(MediaType.APPLICATION_JSON_TYPE).build(); } } catch (SQLException e) { e.printStackTrace(); throw new WebApplicationException(500); } catch (IOException e) { e.printStackTrace(); throw new WebApplicationException(500); } finally { try { db.closeConnection(); } catch (SQLException e) { e.printStackTrace(); } } } }