/* 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.dag; import java.util.ArrayList; import org.mitre.provenance.PLUSException; import org.mitre.provenance.client.ProvenanceClient; import org.mitre.provenance.npe.NonProvenanceEdge; import org.mitre.provenance.plusobject.PLUSEdge; import org.mitre.provenance.plusobject.PLUSObject; import org.mitre.provenance.plusobject.ProvenanceCollection; import org.mitre.provenance.user.User; /** * A collection of provenance nodes and edges that has the context of all being viewable by a given user. ViewedCollection objects also * have a "focus" or a node that the user is most interested in, or caused the graph to be generated in the first place. The focus acts * as an entry point into the graph. * @author moxious */ public class ViewedCollection extends ProvenanceCollection { /** The user that's viewing the DAG */ protected User viewer; public ViewedCollection(User viewer) { super(); if(viewer == null) viewer = User.PUBLIC; this.viewer = viewer; } // End ViewedCollection public ViewedCollection(User viewer, PLUSObject ...objects) { this(viewer); for(PLUSObject obj : objects) addNode(obj); } public ViewedCollection(User viewer, PLUSEdge ...edges) { this(viewer); for(PLUSEdge e : edges) addEdge(e); } public ViewedCollection(User viewer, NonProvenanceEdge ...npes) { this(viewer); for(NonProvenanceEdge e : npes) addNonProvenanceEdge(e); } /** * Change the viewer of this graph. Note that you usually shouldn't do this; if you change the viewer to one with * fewer privileges, this will not guarantee that everything in the collection is still suitable for the new user. * @param user the user who views this DAG. If null, user PUBLIC will be used. */ protected void setViewer(User user) { if(user == null) user = User.PUBLIC; this.viewer = user; } /** * The user associated with this lineage DAG. The DAG contains only items this viewer is permitted to see, * or suitable surrogates for those items. * @return a User whose permissions are used to generate the DAG. */ public User getViewer() { return viewer; } /** * Determine whether or not this collection can contain a given object. Because a ViewedCollection is * tied to a user, the user must have permissions to see the object before this collection can contain it. * @param obj * @return true if the object can be contained as is, false otherwise. */ public boolean canContain(PLUSObject obj) { try { if(getViewer() != null && !ProvenanceClient.instance.canSee(getViewer(), obj)) return false; } catch(PLUSException exc) { log.severe("Cannot determine user's rights on " + obj + ": " + exc.getMessage()); return false; } return true; } // End canContain /** @see ProvenanceCollection#addEdge(PLUSEdge) */ public boolean addEdge(PLUSEdge edge) { return addEdge(edge, false); } /** * Note that if you're adding an edge to a protected version of an object that * is in the graph, the edge will be re-written to point to the surrogate version of the object that is in the graph. * This method sometimes manipulates the edge you give it, because it needs to ensure that you're not adding an edge * into the collection that contains an endpoint the user is not supposed to see. So for example, if you add an * edge going from A -> B, occasionally an edge will be added from A to B's surrogate. * @see ProvenanceCollection#addEdge(PLUSEdge, boolean) */ public boolean addEdge(PLUSEdge edge, boolean force) { boolean fromNeedsUpdate = false; boolean toNeedsUpdate = false; if(containsObjectID(edge.getFrom().getId()) && !contains(edge.getFrom())) { // We have the OID, but the actual object isn't in the collection. This means we have // a surrogate of a more protected version of the object, and we need to update the edge // so it doesn't point to something more sensitive than we should contain. fromNeedsUpdate = true; } else if(!containsObjectID(edge.getFrom().getId())) { // Edge references an object we don't even contain, so just add it. // TODO: If the edge goes to something for which there is no suitable surrogate, // that's a problem. // log.info("Adding missing from object to collection."); if(!addNode(edge.getFrom(), false)) { log.warning("Adding edge requires adding missing FROM node, but it cannot be added to this collection: skipping edge add"); return false; } } if(containsObjectID(edge.getTo().getId()) && !contains(edge.getTo())) { // Same deal as above, on other end of the edge. toNeedsUpdate = true; } else if(!containsObjectID(edge.getTo().getId())) { // TODO: If the edge goes to something for which there is no suitable surrogate, // that's a problem. // log.info("Adding missing to object to collection."); if(!addNode(edge.getTo(), false)) { log.warning("Adding edge requires adding missing TO node, but it cannot be added to this collection."); return false; } } if(fromNeedsUpdate|| toNeedsUpdate) { PLUSEdge copy = edge.clone(); // Replace the from side with the version we have for the object of the same ID if(fromNeedsUpdate) copy.setFrom(getNode(edge.getFrom().getId())); if(toNeedsUpdate) copy.setTo(getNode(edge.getTo().getId())); log.fine("Adding surrogate edge " + copy); return super.addEdge(copy, force); } else return super.addEdge(edge, force); } // End addEdge /** @see ProvenanceCollection#addNode(PLUSObject) */ public boolean addNode(PLUSObject obj) { return addNode(obj, false); } /** * Add a node to the collection. If the node is not visible to the user, an attempt will * be made to calculate a suitable surrogate. If a surrogate cannot be found, then the object * will not be added. * @param obj the object to add * @param force if true, the object will be added even if it is already present. If false, it will only * be added if it is not already in the collection. * @return true if the node was added, false otherwise. */ public boolean addNode(PLUSObject obj, boolean force) { PLUSObject candidate = obj; if(!canContain(candidate)) { try { candidate = candidate.getVersionSuitableFor(viewer); } catch(PLUSException exc) { log.severe("Failed to find suitable version of " + obj + " for " + viewer + ": " + exc.getMessage()); exc.printStackTrace(); candidate = null; } } if(candidate == null) { log.warning("Failed to add node " + obj + " because viewer can't see it! Obj requires " + obj.getPrivileges() + " user presents " + viewer); return false; } boolean success = super.addNode(candidate, force); // log.info("Actually adding " + candidate + " from " + obj + " success? " + success); if(success && (obj != candidate)) { // UGLY. So here's a problem - if we're adding a surrogate, the PLUSEdges in this // object still point at the old (un-surrogate) object. So we have to weed out all of // the edges that refer to the old instance, and update them in this collection to point // to the new surrogate (candidate) // log.info("Node added successfully, but it was a surrogate, so consider " + getEdgesByNode(candidate.getId()) + " edges..."); for(PLUSEdge edge : getEdgesByNode(candidate.getId())) { PLUSEdge modifiedCopy = edge.clone(); if(edge.getFrom() == obj) { modifiedCopy.setFrom(candidate); } else if(edge.getTo() == obj) { modifiedCopy.setTo(candidate); } else { log.severe("Replacing edge on add surrogate: neither end of the edge has the sought object!"); continue; } removeEdge(edge); addEdge(modifiedCopy); log.fine("After adding different surrogate node, replaced " + edge + " with " + modifiedCopy); } } return success; } // End addNode /** * @see ProvenanceCollection#addAll(ProvenanceCollection, boolean) */ public int addAll(ProvenanceCollection other, boolean force) { ProvenanceCollection copy = other.clone(); ArrayList<String>toRemove = new ArrayList<String>(); // Remove the ones that can't go in this graph. for(PLUSObject o : copy.getNodes()) { if(!canContain(o)) { log.warning("Skipping node " + o + " which this graph cannot contain."); toRemove.add(o.getId()); } } for(String oid : toRemove) copy.removeNode(oid); return super.addAll(copy, force); } public ViewedCollection clone() { ViewedCollection col = (ViewedCollection)super.clone(); col.viewer = this.viewer; return col; } // End clone /** @see ProvenanceCollection#addAll(ProvenanceCollection) */ public int addAll(ProvenanceCollection other) { return addAll(other, false); } } // End ViewedCollection