/* Copyright 2014 MITRE Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mitre.provenance.services;
import java.util.ArrayList;
import java.util.Date;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory;
import org.mitre.provenance.db.neo4j.Neo4JStorage;
import org.mitre.provenance.npe.NonProvenanceEdge;
import org.mitre.provenance.plusobject.PLUSActor;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.neo4j.graphdb.Node;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
/**
* Services in this class produce RSS/XML and JSON feeds of the latest objects reported to PLUS in various categories.
* @author moxious
*/
@Path("/feeds")
@Api(value = "/feeds", description = "Feeds of latest reported provenance information")
public class Feeds {
protected static final Logger log = Logger.getLogger(Feeds.class.getName());
protected @Context UriInfo uriInfo;
public static Integer maxResults = 30;
public Feeds() { ; }
/**
* Returns the latest items reported to the database that have hashed content. That is, their metadata
* contains a reference to the field below.
* @param req
* @param maxItems
* @return D3-JSON formatted response
* @see Metadata#CONTENT_HASH_SHA_256
*/
@Path("/hashedContent")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get latest hashed content", notes="Content identified by MD5, SHA hashes", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
@ApiResponse(code = 400, message = "Bad n value"),
})
public Response hashedContent(@Context HttpServletRequest req,
@ApiParam(value = "Maximum number of items to return", required = false)
@DefaultValue("10") @QueryParam("n") int maxItems) {
if(maxItems <= 0 || maxItems > maxResults) return ServiceUtility.BAD_REQUEST("Bad n value");
String propName = Neo4JStorage.getMetadataPropertyName(Metadata.CONTENT_HASH_SHA_256);
String query = "match (n:Provenance) " +
"where has(n.`" + propName + "`) " +
"return n " +
"order by n.created desc " +
"limit " + maxItems;
try {
return ServiceUtility.OK_ExecuteQuery(req, query, "n");
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
}
}
@Path("/connectedData")
@GET
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get latest connected data",
notes="Connected data are data items with both incoming and outgoing provenance links.",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
@ApiResponse(code = 400, message = "Bad n value"),
})
public Response connectedData(@Context HttpServletRequest req,
@ApiParam(value = "Maximum number of items to return", required = false)
@DefaultValue("10") @QueryParam("n") int maxItems) {
if(maxItems <= 0 || maxItems > maxResults) return ServiceUtility.BAD_REQUEST("Bad n value");
String query = "match (a:Provenance)-->(n:Provenance)-->(b:Provenance) " +
"where n.type = 'data' " +
"return n " +
"order by n.created desc " +
"limit " + maxItems;
try {
return ServiceUtility.OK_ExecuteQuery(req, query, "n");
} catch(PLUSException exc) {
exc.printStackTrace();
return ServiceUtility.ERROR(exc.getMessage());
}
} // End connectedData
@Path("/externalIdentifiers")
@GET
@Produces({"application/rss+xml", MediaType.APPLICATION_JSON})
@ApiOperation(value = "Get latest external identifiers (non-provenance IDs)",
notes="Any non-provenance identifiers associated with reported data",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
@ApiResponse(code = 400, message = "Bad n value"),
})
public Response externalIdentifiers(@Context HttpServletRequest req,
@ApiParam(value = "Maximum number of items to return", required = false)
@DefaultValue("30") @QueryParam("n") int maxItems,
@ApiParam(value = "Format of response: rdf or json", required = false)
@DefaultValue("rss") @QueryParam("format") String format) throws PLUSException, FeedException {
if(maxItems < 0) return ServiceUtility.BAD_REQUEST("Bad n value");
if(maxItems > maxResults) maxItems = maxResults;
if(!"rss".equals(format) && !"json".equals(format)) return ServiceUtility.BAD_REQUEST("Illegal format");
ProvenanceCollection col = Neo4JPLUSObjectFactory.getNonProvenanceEdges(null, ServiceUtility.getUser(req), maxItems);
if("json".equals(format)) {
return ServiceUtility.OK(col);
} else {
String title = "Provenance Search Results";
String description = "Feed of external identifiers";
// Set up the syndicated feed
SyndFeed feed = new SyndFeedImpl();
feed.setTitle(title);
feed.setDescription(description);
feed.setUri(uriInfo.getAbsolutePath().toString());
feed.setLink(uriInfo.getAbsolutePath().toString());
feed.setLanguage("en-us" );
feed.setPublishedDate(new Date(System.currentTimeMillis()));
feed.setCopyright("(C) MITRE Corporation 2010");
feed.setFeedType("rss_2.0");
ArrayList<SyndEntry> entries = new ArrayList<SyndEntry>();
for(NonProvenanceEdge npe : col.getNonProvenanceEdges())
entries.add(FeedEntryFactory.getFeedEntry(npe, uriInfo.getAbsolutePath().getPath()));
feed.setEntries(entries);
return ServiceUtility.OK(feed);
} // End else
} // End externalIdentifiers
@Path("/objects/search/{query}")
@GET
@Produces("application/rss+xml")
@ApiOperation(value = "Search for provenance objects by name or description",
notes="Simple keyword or phrase",
response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
})
public Response objectsSearch(@Context HttpServletRequest req,
@ApiParam(value = "user-supplied query string", required = true)
@PathParam("query") String query) throws PLUSException, FeedException {
String title = "Trusting Composed Information: Federated Search Results";
String description = "Feed of objects containing the keyword " + query;
// Set up the syndicated feed
SyndFeed feed = new SyndFeedImpl();
feed.setTitle(title);
feed.setDescription(description);
feed.setUri(uriInfo.getAbsolutePath().toString());
feed.setLink(uriInfo.getAbsolutePath().toString());
feed.setLanguage("en-us" );
feed.setPublishedDate(new Date(System.currentTimeMillis()));
feed.setCopyright("(C) MITRE Corporation 2010");
feed.setFeedType("rss_2.0");
// Fetch the objects
ProvenanceCollection objects = Neo4JPLUSObjectFactory.searchFor(query, ServiceUtility.getUser(req), maxResults);
// Cycle through the objects
ArrayList<SyndEntry> entries = new ArrayList<SyndEntry>();
for(PLUSObject object : objects.getNodesInOrderedList(ProvenanceCollection.SORT_BY_CREATION))
entries.add(FeedEntryFactory.getFeedEntry(object, uriInfo.getAbsolutePath().getPath()));
feed.setEntries(entries);
return ServiceUtility.OK(feed);
}
@Path("/objects/owners/")
@GET
@Produces({"application/rss+xml", MediaType.APPLICATION_JSON})
@ApiOperation(value = "Get latest owners of provenance objects",
notes=" ", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
@ApiResponse(code = 400, message = "Bad n value"),
})
public Response owners(@Context HttpServletRequest req,
@ApiParam(value = "representation format; json or rss", required=true)
@DefaultValue("rss") @QueryParam("format") String format,
@ApiParam(value = "total items to return", required = true)
@QueryParam("n") int maxItems) throws FeedException, PLUSException {
if(!"rss".equals(format) && !"json".equals(format))
return ServiceUtility.BAD_REQUEST("Illegal format '" + format + "' specified.");
String title = "Provenance Feed: Object Owners";
String description = "Feed of object owners";
if(maxItems <= 0) maxItems = 10;
if(maxItems > maxResults) maxItems = maxResults;
// Fetch the objects
log.info("Getting " + maxResults + " actors.");
ProvenanceCollection actors = Neo4JStorage.getActors(maxItems);
log.info("Formatting actor results with " + actors.getActors().size() + " results.");
// TODO consider incorporating permissions into actor reporting.
// User user = ServiceUtility.getUser(req);
if("json".equals(format)) return ServiceUtility.OK(actors);
else {
// Set up the syndicated feed
SyndFeed feed = new SyndFeedImpl();
feed.setTitle(title);
feed.setDescription(description);
feed.setUri(uriInfo.getAbsolutePath().toString());
feed.setLink(uriInfo.getAbsolutePath().toString());
feed.setLanguage("en-us" );
feed.setPublishedDate(new Date(System.currentTimeMillis()));
feed.setCopyright("(C) MITRE Corporation 2010");
feed.setFeedType("rss_2.0");
// Cycle through the objects
ArrayList<SyndEntry> entries = new ArrayList<SyndEntry>();
for(PLUSActor actor : actors.getActors())
entries.add(FeedEntryFactory.getFeedEntry(actor, uriInfo.getAbsolutePath().getPath()));
feed.setEntries(entries);
return ServiceUtility.OK(feed);
} // End else
} // End owners
@Path("/objects/owner/{owner}")
@GET
@Produces({"application/rss+xml", MediaType.APPLICATION_JSON})
@ApiOperation(value = "Get objects owned by a particular actor", notes=" ", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
@ApiResponse(code = 400, message = "Bad n value"),
})
public Response objectsOwner(@Context HttpServletRequest req,
@ApiParam(value = "The actor ID of the owner whose objects you want", required=true)
@PathParam("owner") String ownerID,
@ApiParam(value = "format to return; json or rss", required=true)
@DefaultValue("rss") @QueryParam("format") String format) throws FeedException, PLUSException {
PLUSActor owner = null;
if(!"rss".equals(format) && !"json".equals(format))
return ServiceUtility.BAD_REQUEST("Illegal format '" + format + "' specified.");
Node n = Neo4JStorage.actorExists(ownerID);
if(n == null) return ServiceUtility.NOT_FOUND("No such owner " + ownerID);
owner = Neo4JPLUSObjectFactory.newActor(n);
// Fetch the objects
ProvenanceCollection objects = null;
objects = Neo4JStorage.getOwnedObjects(owner, ServiceUtility.getUser(req), maxResults);
if("rss".equals(format)) {
if(ownerID != null)
owner = Neo4JPLUSObjectFactory.newActor(Neo4JStorage.actorExists(ownerID));
String title = "Provenance Feed: "+owner.getName()+" Provenance Objects";
String description = "Feed of objects reported by " + owner.getName();
// Set up the syndicated feed
SyndFeed feed = new SyndFeedImpl();
feed.setTitle(title);
feed.setDescription(description);
feed.setUri(uriInfo.getAbsolutePath().toString());
feed.setLink(uriInfo.getAbsolutePath().toString());
feed.setLanguage("en-us" );
feed.setPublishedDate(new Date(System.currentTimeMillis()));
feed.setCopyright("(C) MITRE Corporation 2010");
feed.setFeedType("rss_2.0");
// Cycle through the objects
ArrayList<SyndEntry> entries = new ArrayList<SyndEntry>();
for(PLUSObject object : objects.getNodesInOrderedList(ProvenanceCollection.SORT_BY_CREATION))
entries.add(FeedEntryFactory.getFeedEntry(object, uriInfo.getAbsolutePath().getPath()));
feed.setEntries(entries);
return ServiceUtility.OK(feed);
} else {
return ServiceUtility.OK(objects);
}
}
@Path("/objects/latest")
@GET
@Produces({"application/rss+xml", MediaType.APPLICATION_JSON})
@ApiOperation(value = "Get latest reported objects", notes=" ", response=ProvenanceCollection.class)
@ApiResponses(value = {
@ApiResponse(code = 400, message = "Error loading content"),
})
public Response latest(@Context HttpServletRequest req,
@ApiParam(value = "format of response; rss or json", required=true)
@DefaultValue("rss") @QueryParam("format") String format) throws PLUSException, FeedException {
if(!"rss".equals(format) && !"json".equals(format))
return ServiceUtility.BAD_REQUEST("Invalid format");
// Fetch the objects
ProvenanceCollection objects = Neo4JPLUSObjectFactory.getRecentlyCreated(ServiceUtility.getUser(req), maxResults);
// As a special case, since we're reporting this back to the client we have to add in the various actors, otherwise it
// won't deserialize properly.
for(PLUSObject o : objects.getNodes()) {
if(o.getOwner() != null) objects.addActor(o.getOwner());
}
log.info("latest objects: " + objects.countNodes());
if("json".equals(format)) {
return ServiceUtility.OK(objects);
} else {
String title = "Trusting Composed Information Provenance Feed: Latest Objects";
String description = "Feed of the most recently created lineage objects reported to IM-PLUS";
// Set up the syndicated feed
SyndFeed feed = new SyndFeedImpl();
feed.setTitle(title);
feed.setDescription(description);
feed.setUri(uriInfo.getAbsolutePath().toString());
feed.setLink(uriInfo.getAbsolutePath().toString());
feed.setLanguage("en-us" );
feed.setPublishedDate(new Date(System.currentTimeMillis()));
feed.setCopyright("(C) MITRE Corporation 2010");
feed.setFeedType("rss_2.0");
// Cycle through the objects
ArrayList<SyndEntry> entries = new ArrayList<SyndEntry>();
for(PLUSObject object : objects.getNodesInOrderedList(ProvenanceCollection.SORT_BY_CREATION))
entries.add(FeedEntryFactory.getFeedEntry(object, uriInfo.getAbsolutePath().getPath()));
feed.setEntries(entries);
return ServiceUtility.OK(feed);
} // End else
}
} // End Feeds