/* 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.HashMap; import java.util.HashSet; import java.util.List; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.mitre.provenance.PLUSException; import org.mitre.provenance.dag.TraversalSettings; import org.mitre.provenance.dag.ViewedCollection; import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory; import org.mitre.provenance.db.neo4j.Neo4JStorage; import org.mitre.provenance.plusobject.PLUSActor; import org.mitre.provenance.plusobject.PLUSObject; import org.mitre.provenance.tools.PLUSUtils; import org.mitre.provenance.user.User; import org.neo4j.cypher.javacompat.ExecutionResult; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.ResourceIterator; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.TransactionFailureException; import org.neo4j.graphdb.traversal.TraversalDescription; 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 which drive the "fitness widget" functionality. * These services provide small custom analyses of provenance graphs, * and return the structures for display. * * <p>Most of these functions are menat to operate over graph structures in the * database that are too large to be loaded into memory. For that reason, they do not * provide interfaces that return ProvenanceCollection objects. * * @author moxious */ @Path("/fitness") @Api(value = "/fitness", description = "Provenance Fitness Assessments") public class FitnessServices { protected static Logger log = Logger.getLogger(FitnessServices.class.getName()); @GET @Path("/{oid:.*}/timelag") @ApiOperation(value = "Assess the time lag (oldest to newest) in a graph", notes="") @ApiResponses(value = { @ApiResponse(code = 404, message="No such object"), }) public Response timeLag(@Context HttpServletRequest req, @ApiParam(value="Object ID as starting point", required=true) @PathParam("oid") String oid) throws PLUSException { Node n = Neo4JStorage.oidExists(oid); if(n == null) return ServiceUtility.NOT_FOUND("No such object"); TraversalSettings settings = new TraversalSettings(); settings.forward = false; settings.backward = true; settings.n = 500; settings.maxDepth = 500; settings.breadthFirst = false; // Go depth first to discover old stuff. settings.followNPIDs = false; settings.includeEdges = true; settings.includeNodes = true; settings.includeNPEs = false; User user = ServiceUtility.getUser(req); final HashSet<Long>seen = new HashSet<Long>(); final PLUSObject [] oldestAndNewest = new PLUSObject[]{null, null}; try (Transaction tx = Neo4JStorage.beginTx()) { TraversalDescription desc = Neo4JPLUSObjectFactory.buildTraversalDescription(settings); // Visit each node, update oldest and newest accordingly. for(Node visit : desc.traverse(n).nodes()) { if(seen.contains(visit.getId())) continue; try { PLUSObject obj = Neo4JPLUSObjectFactory.newObject(visit); if(obj == null) { log.warning("Null object on node " + visit); continue; } obj = obj.getVersionSuitableFor(user); if(obj == null) return ServiceUtility.FORBIDDEN("You do not have access to information necessary to complete this request."); if(oldestAndNewest[0]==null || obj.getCreated()<oldestAndNewest[0].getCreated()) oldestAndNewest[0]=obj; if(oldestAndNewest[1]==null || obj.getCreated()>oldestAndNewest[1].getCreated()) oldestAndNewest[1]=obj; } catch(PLUSException exc) { exc.printStackTrace(); } } // End for tx.success(); } catch(TransactionFailureException exc) { log.severe("Failed transaction: " + exc.getMessage()); } HashMap<String,Object> map = new HashMap<String,Object>(); long span = oldestAndNewest[1].getCreated() - oldestAndNewest[0].getCreated(); // Display the newest and oldest nodes if the DAG has nodes map.put("oldest", oldestAndNewest[0].getId()); map.put("newest", oldestAndNewest[1].getId()); map.put("timespan", PLUSUtils.describeTimeSpan(span)); return ServiceUtility.OK(map); } // End timeLag @GET @Path("/{oid:.*}/termFinder") public Response termFinder(@Context HttpServletRequest req, @PathParam("oid") String oid, @QueryParam("term") String term) throws PLUSException { if(term == null || "".equals(term) || "".equals(term.trim())) return ServiceUtility.BAD_REQUEST("Missing term"); User user = ServiceUtility.getUser(req); ViewedCollection col = new ViewedCollection(user); final Node startingPoint = Neo4JStorage.oidExists(oid); if(startingPoint == null) return ServiceUtility.NOT_FOUND("No such object " + oid); try (Transaction tx = Neo4JStorage.beginTx()) { TraversalSettings s = new TraversalSettings(); s.n = 500; s.maxDepth = -1; s.backward = true; s.forward = false; s.breadthFirst = true; s.followNPIDs = false; s.includeNodes = true; s.includeEdges = false; s.includeNPEs = false; TraversalDescription desc = Neo4JPLUSObjectFactory.buildTraversalDescription(s); final String termToFind = term.toLowerCase().trim(); for(Node n : desc.traverse(startingPoint).nodes()) { String name = (""+n.getProperty("name", "")).toLowerCase(); if(name.indexOf(termToFind) != -1) col.addNode(Neo4JPLUSObjectFactory.newObject(n)); } // End for tx.success(); } catch(TransactionFailureException exc) { log.severe("Failed transaction: " + exc.getMessage()); } return ServiceUtility.OK(col, req); } // End termFinder @GET @Path("/{oid:.*}/summary") public Response summary(@Context HttpServletRequest req, @PathParam("oid") String oid) throws PLUSException { HashMap<String,Object> map = new HashMap<String,Object>(); User user = ServiceUtility.getUser(req); Node n = Neo4JStorage.oidExists(oid); if(n == null) return ServiceUtility.NOT_FOUND("No such object " + oid); try { PLUSObject o = Neo4JPLUSObjectFactory.newObject(n); if(o == null) return ServiceUtility.ERROR("Could not load object " + oid); // make sure to give the user one they can see. o = o.getVersionSuitableFor(user); String type = o.getObjectType() + "/" + o.getObjectSubtype(); Date d = o.getCreatedAsDate(); map.put("name", o.getName()); map.put("type", type); map.put("created", ""+d); PLUSActor owner = o.getOwner(); map.put("summary", o.getName() + " (" + type + ") created " + d + " by " + (owner == null ? "unknown" : owner.getName()) + " with " + o.getMetadata().size() + " metadata items."); return ServiceUtility.OK(map); } catch(Exception exc) { exc.printStackTrace(); return ServiceUtility.ERROR(exc.getMessage()); } } // End summary @GET @Path("/{oid:.*}/custody") public Response custody(@Context HttpServletRequest req, @PathParam("oid") String oid) throws PLUSException { /* * sampe query for path collationl. * start c = node:node_auto_index ( object_id = '10179' ) MATCH path = c <- [ : PARENT_OF* ] – p return distinct length ( path ) AS PATH_LENGTH , extract ( n in nodes ( path ) : n.object_id ) as the_path order by length ( path ) */ String query = "start myTarget=node:node_auto_index(oid={oid}) " + "match m-[r:contributed|marks|`input to`|unspecified|triggered|generated*]->myTarget " + "where has(m.ownerid) " + "return m.ownerid as ownerid"; List<HashMap<String,Object>> owners = new ArrayList<HashMap<String,Object>>(); try (Transaction tx = Neo4JStorage.beginTx()) { User user = ServiceUtility.getUser(req); log.info("custody(" + oid + ", " + user + ")"); HashMap<String,Object>params = new HashMap<String,Object>(); params.put("oid", oid); ExecutionResult r = Neo4JStorage.execute(query, params); ResourceIterator<Object> ownerids = r.columnAs("ownerid"); while(ownerids.hasNext()) { String id = (String) ownerids.next(); if(id == null || "".equals(id)) { log.info("Found empty ownerid " + id); continue; } else { log.info("Found ownerid " + id); PLUSActor a = Neo4JPLUSObjectFactory.newActor(Neo4JStorage.actorExists(id)); HashMap<String,Object> jsonObj = new HashMap<String,Object>(); jsonObj.put("name", a.getName()); jsonObj.put("type", a.getType()); jsonObj.put("created", a.getCreated()); jsonObj.put("aid", a.getId()); owners.add(jsonObj); log.info("Added actor " + a); } // End else } ownerids.close(); tx.success(); } catch(TransactionFailureException exc) { log.severe("Failed transaction: " + exc.getMessage()); } return ServiceUtility.OK(owners); } // End custody } // End FitnessServices