/* 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.client; import java.io.IOException; import java.util.List; import java.util.logging.Logger; import org.mitre.provenance.Metadata; import org.mitre.provenance.PLUSException; import org.mitre.provenance.dag.TraversalSettings; 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.plusobject.PLUSWorkflow; import org.mitre.provenance.plusobject.ProvenanceCollection; import org.mitre.provenance.user.PrivilegeClass; import org.mitre.provenance.user.User; import org.neo4j.cypher.javacompat.ExecutionResult; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Relationship; import org.neo4j.graphdb.ResourceIterator; import org.neo4j.graphdb.Transaction; import org.neo4j.graphdb.TransactionFailureException; /** * This class permits the use of a provenance client attached to a local database. This local class is essentially a * wrapper around a number of methods found in the storage layer. * * <p>Use this class to report provenance to a local database on disk. * * @author moxious */ public class LocalProvenanceClient extends AbstractProvenanceClient { protected User user = User.PUBLIC; protected static Logger log = Logger.getLogger(LocalProvenanceClient.class.getName()); public LocalProvenanceClient() { this(User.PUBLIC); } /** * Create a local client with a given user; all requests will be made as that user. * @param u the user to use. */ public LocalProvenanceClient(User u) { this.user = u; } public boolean report(ProvenanceCollection col) throws ProvenanceClientException { try { if(Neo4JStorage.store(col) > 0) return true; return false; } catch(PLUSException exc) { throw new ProvenanceClientException(exc); } } public ProvenanceCollection getGraph(String oid, TraversalSettings desc) throws ProvenanceClientException { // TODO Auto-generated method stub try { return Neo4JPLUSObjectFactory.newDAG(oid, user, desc); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } public PLUSObject exists(String oid) throws ProvenanceClientException { org.neo4j.graphdb.Node n = Neo4JStorage.oidExists(oid); if(n == null) return null; try { return Neo4JPLUSObjectFactory.newObject(n); } catch (PLUSException e) { throw new ProvenanceClientException("Cannot convert object node", e); } } public ProvenanceCollection latest() throws ProvenanceClientException { // TODO Auto-generated method stub return Neo4JPLUSObjectFactory.getRecentlyCreated(user, 20); } public ProvenanceCollection getActors(int max) throws ProvenanceClientException { try { return Neo4JStorage.getActors(max); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } public ProvenanceCollection search(String searchTerm, int max) throws ProvenanceClientException { return Neo4JPLUSObjectFactory.searchFor(searchTerm, viewer, max); } public ProvenanceCollection search(Metadata parameters, int max) throws ProvenanceClientException { try { return Neo4JPLUSObjectFactory.loadByMetadata(viewer, parameters, max); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } public List<PLUSWorkflow> listWorkflows(int max) throws ProvenanceClientException { try { return Neo4JStorage.listWorkflows(user, max); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } public ProvenanceCollection getWorkflowMembers(String oid, int max) throws ProvenanceClientException { PLUSObject obj = getSingleNode(oid); if(obj == null) throw new ProvenanceClientException("Can not load workflow members from non-existant node " + oid); if(!obj.isWorkflow()) throw new ProvenanceClientException("Can not load workflow members from non-workflow " + oid); return Neo4JStorage.getMembers((PLUSWorkflow)obj, user, max); } // End getWorkflowMembers public PLUSObject getSingleNode(String oid) throws ProvenanceClientException { try { return Neo4JPLUSObjectFactory.load(oid, user); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } // End getSingleNode public PLUSActor actorExists(String aid) throws ProvenanceClientException { org.neo4j.graphdb.Node n = Neo4JStorage.actorExists(aid); if(n != null) { try { return Neo4JPLUSObjectFactory.newActor(n); } catch (PLUSException e) { throw new ProvenanceClientException("Cannot convert actor", e); } } return null; } public PLUSActor actorExistsByName(String name) throws ProvenanceClientException { org.neo4j.graphdb.Node n = Neo4JStorage.actorExistsByName(name); if(n != null) { try { return Neo4JPLUSObjectFactory.newActor(n); } catch (PLUSException e) { throw new ProvenanceClientException("Cannot convert actor", e); } } return null; } public boolean dominates(PrivilegeClass a, PrivilegeClass b) throws ProvenanceClientException { try { return Neo4JStorage.dominates(a, b); } catch (PLUSException e) { throw new ProvenanceClientException(e); } } /** * TODO: code in this method should be refactored into Neo4JStorage, then called * again in the service layer for reuse, better internal settings. */ public ProvenanceCollection query(String query) throws IOException { ProvenanceCollection col = new ProvenanceCollection(); try (Transaction tx = Neo4JStorage.beginTx()) { log.info("Query for " + query); ExecutionResult rs = Neo4JStorage.execute(query); int limit = 500; for(String colName : rs.columns()) { int x=0; ResourceIterator<?> it = rs.columnAs(colName); while(it.hasNext() && x < limit) { Object next = it.next(); if(next instanceof Node) { if(Neo4JStorage.isPLUSObjectNode((Node)next)) col.addNode(Neo4JPLUSObjectFactory.newObject((Node)next)); else { log.info("Skipping non-provnenace object node ID " + ((Node)next).getId()); continue; } } else if(next instanceof Relationship) { Relationship rel = (Relationship)next; if(Neo4JStorage.isPLUSObjectNode(rel.getStartNode()) && Neo4JStorage.isPLUSObjectNode(rel.getEndNode())) { col.addNode(Neo4JPLUSObjectFactory.newObject(rel.getStartNode())); col.addNode(Neo4JPLUSObjectFactory.newObject(rel.getEndNode())); col.addEdge(Neo4JPLUSObjectFactory.newEdge(rel)); } else { log.info("Skipping non-provenace edge not yet supported " + rel.getId()); } } } // End while it.close(); if((col.countEdges() + col.countNodes()) >= limit) break; } tx.success(); } catch(TransactionFailureException tfe) { // Sometimes neo4j does the wrong thing, and throws these exceptions failing to commit // on simple read-only queries. Which doesn't make sense. Subject to a bug report. log.warning("Transaction failed when searching graph: " + tfe.getMessage() + " / " + tfe); } catch(Exception exc) { exc.printStackTrace(); } return col; } } // End LocalProvenanceClient