/* 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.plusobject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.npe.NonProvenanceEdge;
import org.mitre.provenance.tools.PLUSUtils;
/**
* A collection of provenance objects, nodes and edges.
* Basic provenance collections cannot be assumed to be well-connected. They do not necessarily comprise a graph. See LineageDAG
* for an example of an actual graph, which extends this class.
* @author moxious
* @see org.mitre.provenance.dag.LineageDAG
*/
public class ProvenanceCollection implements Cloneable {
protected static Logger log = Logger.getLogger(ProvenanceCollection.class.getName());
public static final String TAINT_FLAG = "dag:taint";
public static final String TAG_VALUE_TRUE = "true";
public static final String TAG_FOOT = "foot";
public static final String TAG_HEAD = "head";
/**
* This comparator will sort first by created, then name, then type, then subtype, then ID.
*/
public static final Comparator<PLUSObject> SORT_BY_CREATION = new Comparator <PLUSObject> () {
public int compare(PLUSObject object1, PLUSObject object2) {
int cmp = Long.valueOf(object2.getCreated()).compareTo(object1.getCreated());
if(cmp != 0) return cmp;
cmp = object1.getName().compareTo(object2.getName());
if(cmp != 0) return cmp;
cmp = object1.getObjectType().compareTo(object2.getObjectType());
if(cmp != 0) return cmp;
cmp = object1.getObjectSubtype().compareTo(object2.getObjectSubtype());
if(cmp != 0) return cmp;
// Last resort.
return object1.getId().compareTo(object2.getId());
}
};
public static final Comparator<PLUSObject> SORT_BY_NAME = new Comparator<PLUSObject> ()
{ public int compare(PLUSObject o1, PLUSObject o2)
{ return o1.getName().compareTo(o2.getName()); } };
/** Maps an actor ID to an actor */
protected Map <String,PLUSActor> actors;
/** Maps object or surrogate IDs to objects */
protected Map <String,PLUSObject> nodes;
/** Maps a special edge string key (id1-id2) to an edge */
protected Map <String,PLUSEdge> edges;
/** Maps a special edge string key (id1-id2) to an NPE */
protected Map <String,NonProvenanceEdge> npes;
/** ID for this collection */
protected String id;
/** A way of associating text tags with nodes for later processing */
protected Map <String, HashMap<String,String>> nodeTags;
/** A way of storing extra junk about the collection we'll need later */
protected Metadata metadata;
public ProvenanceCollection() {
id = PLUSUtils.generateID();
empty();
} // End ProvenanceCollection
public ProvenanceCollection(Iterable<SourcedObject> objs) {
this();
for(SourcedObject obj : objs) {
if(obj instanceof PLUSEdge) addEdge((PLUSEdge)obj);
else addNode((PLUSObject)obj);
}
} // End ProvenanceCollection
public String getId() { return id; }
public void setId(String id) { this.id = id; }
/**
* Empties the contents of the collection, including metadata.
*/
protected void empty() {
nodes = new HashMap<String,PLUSObject>();
edges = new HashMap<String,PLUSEdge>();
npes = new HashMap<String,NonProvenanceEdge>();
actors = new HashMap<String,PLUSActor>();
metadata = new Metadata();
metadata.setOwnerOID(id);
nodeTags = new HashMap<String,HashMap<String,String>>();
} // End empty
/**
* Gets the metadata object associated with the collection.
* @return a metadata object
*/
public Metadata getMetadata() { return metadata; }
public void setMetadata(Metadata md) {
this.metadata = new Metadata();
for(Object k : md.keySet()) metadata.put(k.toString(), ""+md.get(k));
this.metadata.setOwnerOID(id);
} // End setMetadata
/**
* @param aid an actor's ID
* @return true if the collection contains the actor, false otherwise.
*/
public boolean containsActorID(String aid) {
if(aid == null) return false;
return actors.containsKey(aid);
}
/**
* NOTE! Because of surrogates, keep in mind that there can be multiple different objects
* by the same ID - you might want to use the regular contains() method if you're looking for a particular
* object instance.
* @param oid a PLUSObject oid from the database
* @return true if the collection contains an object by that ID
*/
public boolean containsObjectID(String oid) {
return nodes.containsKey(oid);
}
/**
* Determine whether a provenance object is in the collection. It must be the same actual reference;
* a surrogate for a given OID will not match the original object.
* @param obj a PLUSObject
* @return true if the object is in the provenance collection, false otherwise.
*/
public boolean contains(PLUSObject obj) {
return nodes.containsKey(obj.getId()) && (nodes.get(obj.getId()) == obj);
}
/**
* Determine whether an actor is in the collection. It must be the same actual reference.
* @param actor the actor to check
* @return true if that object is in the collection, false otherwise.
*/
public boolean contains(PLUSActor actor) {
return actors.containsKey(actor.getId()) && (actors.get(actor.getId()) == actor);
}
/**
* @param npe a non-provenance edge
* @return true if the collection contains that NPE, false otherwise.
*/
public boolean contains(NonProvenanceEdge npe) {
return npes.containsKey(getHashKey(npe));
} // End contains
/**
* @param edge a PLUSEdge
* @return true if the collection contains that edge, false otherwise.
*/
public boolean contains(PLUSEdge edge) {
return edges.containsKey(getHashKey(edge));
}
/**
* @param oid a PLUSObject oid
* @return the PLUSObject, or null if it is not in the collection
*/
public PLUSObject getNode(String oid) {
PLUSObject obj = nodes.get(oid);
// if(obj == null) log.warn("getNode: " + oid + " not present!");
return obj;
} // End getNode
/** Return the total number of actors in the collection. */
public int countActors() { return actors.size(); }
/** Return the total number of nodes in the collection */
public int countNodes() { return nodes.size(); }
/** Return the total number of edges in the collection */
public int countEdges() { return edges.size(); }
/** Return the total number of NPEs in the collection */
public int countNPEs() { return npes.size(); }
/**
* Get a particular edge from the collection.
* @param from_oid ID of the from side
* @param to_oid ID of the to side
* @return a PLUSEdge that's in the collection, or null if there is no such edge.
*/
public PLUSEdge getEdge(String from_oid, String to_oid) {
return edges.get(getHashKey(from_oid, to_oid));
}
/**
* Get a particular edge from the collection
* @param from an object that originates the edge
* @param to an object that terminates the edge
* @return a PLUSEdge that's in the collection, or null if there is no such edge.
*/
public PLUSEdge getEdge(PLUSObject from, PLUSObject to) {
return getEdge(from.getId(), to.getId());
}
/**
* Remove an edge from the collection.
* @param edge the edge to remove.
*/
public void removeEdge(PLUSEdge edge) {
edges.remove(getHashKey(edge));
} // End removeEdge
/**
* Remove a non-provenance edge from the collection.
* @param npe the NPE to remove.
*/
public void removeNonProvenanceEdge(NonProvenanceEdge npe) {
npes.remove(getHashKey(npe));
}
/** Retrieves all inbound edges for the specified node */
public List<PLUSEdge> getInboundEdgesByNode(String oid)
{ return getEdgesByPattern("->" + oid); }
/** Retrieves all outbound edges for the specified node */
public List<PLUSEdge> getOutboundEdgesByNode(String oid)
{ return getEdgesByPattern(oid + "->"); }
/** Retrieves all edges incident to the specified node. */
public List<PLUSEdge> getEdgesByNode(String oid)
{ return getEdgesByPattern(oid); }
/** Retrieves all edges based on the specified pattern */
protected List<PLUSEdge> getEdgesByPattern(String pattern) {
ArrayList <PLUSEdge> ret = new ArrayList <PLUSEdge> ();
for(String key : edges.keySet())
if(key.contains(pattern)) ret.add(edges.get(key));
return ret;
}
public List<PLUSEdge> getEdgesByString(String pattern) {
return getEdgesByPattern(pattern);
}
public PLUSActor getActor(String actorID) { return actors.get(actorID); }
/** Get the actors in this ProvenanceCollection */
public Collection <PLUSActor> getActors() { return actors.values(); }
/** Get the nodes in this ProvenanceCollection */
public Collection <PLUSObject> getNodes() { return nodes.values(); }
/** Get the edges in this ProvenanceCollection */
public Collection <PLUSEdge> getEdges() { return edges.values(); }
/** Get the non-provenance edges in this provenance collection */
public Collection <NonProvenanceEdge> getNonProvenanceEdges() { return npes.values(); }
/**
* Get the non-provenance edges that are incident to a given object.
* @param obj an object in the collection
* @return the list of NPEs incident to that object, or an empty list if there are none, or if the object isn't in the collection.
*/
public List<NonProvenanceEdge> getNonProvenanceEdges(PLUSObject obj) {
ArrayList<NonProvenanceEdge> r = new ArrayList<NonProvenanceEdge>();
for(String k : npes.keySet()) {
if(k.contains(obj.getId())) r.add(npes.get(k));
}
return r;
} // End getNonProvenanceEdge
/**
* Equivalent to addEdge(edge, false)
* @see ProvenanceCollection#addEdge(PLUSEdge, boolean)
*/
public boolean addEdge(PLUSEdge edge) { return addEdge(edge, false); }
/**
* Add an edge to the collection.
* @param edge the edge to add.
* @param force if true, this will be added overwriting anything similar already there. If false, the edge
* will be added only if its candidate nodes aren't already connected.
* @return true if the edge was added, false otherwise.
*/
public boolean addEdge(PLUSEdge edge, boolean force) {
if(edge == null) return false;
String key = getHashKey(edge);
if(force) {
edges.put(key, edge);
return true;
} else if(!edges.containsKey(key)) {
edges.put(key, edge);
return true;
} // End else if
return false;
} // End addEdge
/** Used to calculate the key under which an edge will be stored. */
private String getHashKey(String from_oid, String to_oid) {
return from_oid + "->" + to_oid;
}
/** Used to calculate the key under which the edge will be stored. */
private String getHashKey(PLUSEdge e) {
return getHashKey(e.getFrom().getId(), e.getTo().getId());
}
/** Used to calculate the key under which the edge will be stored. */
private String getHashKey(NonProvenanceEdge npe) {
return getHashKey(npe.getFrom(), npe.getTo());
}
/**
* Add a specified NPE to the provenance collection
* @param npe the NPE to add
* @return true if it was added, false if it was not (i.e. the edge already existed)
*/
public boolean addNonProvenanceEdge(NonProvenanceEdge npe) {
return addNonProvenanceEdge(npe, false);
}
/**
* Add a specified NPE to the provenance collection.
* @param npe the NPE to add
* @param force if true, the NPE is guaranteed to be added. If false, the NPE will be added only if it doesn't already exist.
* @return true if the NPE was added, false otherwise.
*/
public boolean addNonProvenanceEdge(NonProvenanceEdge npe, boolean force) {
if(npe == null) return false;
String key = getHashKey(npe);
if(force) {
npes.put(key, npe);
return true;
} else if(!npes.containsKey(key)) {
npes.put(key, npe);
return true;
}
return false;
} // End addNonProvenanceEdge
/** Remove an actor */
public PLUSActor removeActor(PLUSActor a) { return actors.remove(a); }
/** Equivalent to addActor(a, false) */
public boolean addActor(PLUSActor a) { return addActor(a, false); }
/** Equivalent to addNode(obj, false); **/
public boolean addNode(PLUSObject obj) { return addNode(obj, false); }
/**
* Add a particular actor to the graph
* @param a the actor to add
* @param force if true, add this actor even if it is already present, overwriting what was previously there.
* If false, ignore the actor if it is already present.
* @return true if it was added, false otherwise.
*/
public boolean addActor(PLUSActor a, boolean force) {
if(a == null) return false;
if(actors.containsKey(a.getId()) && !force) return false;
actors.put(a.getId(), a);
return true;
}
/**
* Add a particular node to the graph
* @param obj the node to add.
* @param force if true, add this node to the graph even if it is already present, overwriting what was previously there.
* If false, ignore the node if it is already present.
* @return true if it was added, false otherwise.
*/
public boolean addNode(PLUSObject obj, boolean force) {
if(obj == null) return false;
if(nodes.containsKey(obj.getId()) && !force) return false;
// log.debug("***addNode: Adding object for " +obj.getName() + " id " + obj.getId() + ": " + obj);
nodes.put(obj.getId(), obj);
return true;
} // End addNode
/**
* Remove a node from the provenance collection.
* @param node the node to remove
* @param removeIncidentEdges if true, incident edges to that node will also be removed. If false, they will remain.
* @return the node removed (if it was in the collection) or null if it did not exist in the collection.
*/
public PLUSObject removeNode(PLUSObject node, boolean removeIncidentEdges) {
if(node == null) return null;
if(!removeIncidentEdges) return removeNode(node.getId());
else {
for(PLUSEdge e : getEdgesByNode(node.getId()))
removeEdge(e);
return removeNode(node.getId());
}
}
/**
* Remove a node from the provenance collection. Does not remove incident edges.
* @param node the node to remove
* @return the node removed (if it was in the collection) or null if the node did not exist in the collection.
* @see ProvenanceCollection#removeNode(PLUSObject, boolean)
*/
public PLUSObject removeNode(PLUSObject node) { return removeNode(node, false); }
/**
* Remove a node from the collection identified by an OID. Does not remove the edges incident to that node.
* @param oid the ID of the node to remove
* @return the removed node, or null if it was not in the collection.
* @see ProvenanceCollection#removeNode(PLUSObject, boolean)
*/
public PLUSObject removeNode(String oid) {
return nodes.remove(oid);
} // End removeNode
public PLUSObject removeNode(String oid, boolean removeIncidentEdges) {
return removeNode(getNode(oid), removeIncidentEdges);
}
public String toString() {
return ("ProvenanceCollection with " + countNodes() + " nodes " + countEdges() + " edges " +
metadata.size() + " metadata " + countActors() + " actors " + countNPEs() + " NPEs");
} // End toString()
/** Equivalent to addAll(other, false); */
public int addAll(ProvenanceCollection other) { return addAll(other, false); }
public ProvenanceCollection clone() {
ProvenanceCollection pc;
try {
pc = (ProvenanceCollection)super.clone();
} catch (CloneNotSupportedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return null;
}
Metadata md = new Metadata();
for(String k : getMetadata().keySet()) md.put(k, getMetadata().get(k));
pc.setMetadata(md);
pc.setId(getId());
for(PLUSActor actor : getActors()) pc.addActor(actor);
for(PLUSObject o : getNodes()) pc.addNode(o);
for(PLUSEdge e : getEdges()) pc.addEdge(e);
for(NonProvenanceEdge npe : getNonProvenanceEdges()) pc.addNonProvenanceEdge(npe);
for(String k : nodeTags.keySet()) {
pc.nodeTags.put(k, (HashMap<String,String>)nodeTags.get(k).clone());
}
return pc;
} // End makeCopy
/**
* Take all items from another provenance collection and add them to this one.
* @param other the other provenance collection
* @param force if true, things in "other" will override whatever may be present.
* If false, only items that don't already exist will be copied.
*/
public int addAll(ProvenanceCollection other, boolean force) {
int itemsAdded = 0;
Map<String,PLUSObject> on = other.nodes;
Map<String,PLUSEdge> oe = other.edges;
Map<String,NonProvenanceEdge> npes = other.npes;
Map<String,PLUSActor> oa = other.actors;
for(String k : on.keySet()) {
if(addNode(on.get(k), force)) itemsAdded++;
}
for(String k : oe.keySet()) {
if(addEdge(oe.get(k), force)) itemsAdded++;
}
for(String k : oa.keySet()) {
if(addActor(oa.get(k), force)) itemsAdded++;
}
for(String k : npes.keySet()) {
if(addNonProvenanceEdge(npes.get(k), force)) itemsAdded++;
}
for(String k : other.getMetadata().keySet()) {
if(force || !metadata.contains(k))
metadata.put(k, other.getMetadata().get(k));
} // End for
return itemsAdded;
} // End addAll
public void tagNode(PLUSObject node, String tag, String value) { tagNode(node.getId(), tag, value); }
/**
* Associate a particular string tag with a node.
* @param oid the node you want to tag
* @param tag the tag to associated with it.
*/
public void tagNode(String oid, String tag, String value) {
HashMap<String,String> tags = nodeTags.get(oid);
if(tags == null) {
tags = new HashMap<String,String>();
nodeTags.put(oid, tags);
}
if(!tags.containsKey(tag)) tags.put(tag, value);
} // End tagNode
public Set<String> getTaggedNodes() {
return nodeTags.keySet();
}
/**
* Return the tags assocated with a node. Always guaranteed to return an ArrayList (not null) so if no tags
* exist, an empty ArrayList will return.
* @param oid the ID of the PLUSObject.
* @return an ArrayList of String tags.
*/
public HashMap<String,String> getTags(String oid) {
HashMap<String,String> tags = nodeTags.get(oid);
if(tags == null) tags = new HashMap<String,String>();
return tags;
} // End getTags
public boolean hasTag(String oid, String tag) {
return hasTag(oid, tag, false);
}
public boolean removeTag(String oid, String tag) {
HashMap<String,String> tags = getTags(oid);
return tags.remove(tag) != null;
} // End removeTag
public List<PLUSEdge>getEdgesInList() {
ArrayList<PLUSEdge>list = new ArrayList<PLUSEdge>();
for(PLUSEdge e : getEdges()) list.add(e);
return list;
}
/** Returns nodes in an ordered list, sorted by creation time. */
public List<PLUSObject> getNodesInOrderedList() {
return getNodesInOrderedList(SORT_BY_CREATION);
}
/**
* Return nodes in an ordered list, ordered by a given comparator.
* @param ordering if null, items will be sorted by creation time.
* @return a list of nodes in this provenance collection, sorted with the given comparator.
*/
public List<PLUSObject>getNodesInOrderedList(Comparator <PLUSObject> ordering) {
ArrayList<PLUSObject>list = new ArrayList<PLUSObject>(getNodes());
if(ordering == null) ordering = SORT_BY_CREATION;
Collections.sort(list, ordering);
return list;
} // End getNodesInOrderedList
/**
* @param oid the object that was tagged
* @param tag the tag you're checking for
* @param contains if true, this method will look for substring tags. If false, it will look for exact matches.
* @return true if that object has that tag, false otherwise.
*/
public boolean hasTag(String oid, String tag, boolean contains) {
HashMap<String,String> tags = getTags(oid);
for(String s : tags.keySet()) {
if(contains) {
if(s.toLowerCase().contains(tag.toLowerCase())) return true;
} else {
if(s.equalsIgnoreCase(tag)) return true;
} // End else
} // End for
return false;
} // End hasTag
public boolean isEmpty() {
return (countNodes() <= 0) && (countEdges() <= 0);
}
public List<String> graphLint() {
ArrayList<String> issues = new ArrayList<String>();
for(PLUSEdge e : getEdges()) {
if(!contains(e.getFrom())) issues.add("Edge " + e + " names " + e.getFrom() + " which isn't in the collection.");
if(!contains(e.getTo())) issues.add("Edge " + e + " names " + e.getTo() + " which isn't in the collection.");
}
return issues;
} // End graphLint
/**
* Create a new collection from a list of objects.
* @param objs objects to collect
* @return a new collection containing the specified objects.
*/
public static ProvenanceCollection collect(PLUSObject ... objs) {
ProvenanceCollection c = new ProvenanceCollection();
for(PLUSObject o : objs) c.addNode(o);
return c;
}
/**
* Create a new collection from a list of edges.
* @param edges edges to collect
* @return a new collection containing the specified edges
*/
public static ProvenanceCollection collect(PLUSEdge ... edges) {
ProvenanceCollection c = new ProvenanceCollection();
for(PLUSEdge e : edges) c.addEdge(e);
return c;
}
/**
* Create a new collection from a list of actors.
* @param actors the actors to collect
* @return a new collection containing the specified actors
*/
public static ProvenanceCollection collect(PLUSActor ... actors) {
ProvenanceCollection c = new ProvenanceCollection();
for(PLUSActor a : actors) c.addActor(a);
return c;
}
/**
* Create a new collection from a list of NPEs.
* @param npes the NPEs to collect
* @return a new collection containing the specified NPEs
*/
public static ProvenanceCollection collect(NonProvenanceEdge ... npes) {
ProvenanceCollection c = new ProvenanceCollection();
for(NonProvenanceEdge npe : npes) c.addNonProvenanceEdge(npe);
return c;
}
} // End ProvenanceCollection