/* 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.json;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.PLUSException;
import org.mitre.provenance.dag.FingerPrint;
import org.mitre.provenance.dag.LineageDAG;
import org.mitre.provenance.db.neo4j.Neo4JPLUSObjectFactory;
import org.mitre.provenance.npe.NonProvenanceEdge;
import org.mitre.provenance.plusobject.PLUSActor;
import org.mitre.provenance.plusobject.PLUSEdge;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.ProvenanceCollection;
import org.mitre.provenance.user.PrivilegeClass;
import org.mitre.provenance.user.PrivilegeSet;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
/**
* An object that knows how to take JSON objects and turn them into ProvenanceCollections.
* @author moxious
*/
public class JSONConverter {
private static Logger log = Logger.getLogger(JSONConverter.class.getName());
public static final String KEY_FROM = "from";
public static final String KEY_TO = "to";
public static final String KEY_TYPE = "type";
public static final String KEY_SUBTYPE = "subtype";
public static final String KEY_NAME = "name";
public static final String KEY_ID = "id";
public static final String KEY_LABEL = "label";
public static final String KEY_WORKFLOW = "workflow";
public static final String KEY_CREATED = "created";
public static final String KEY_SOURCE = "source";
public static final String KEY_TARGET = "target";
public static final String KEY_LEFT = "left";
public static final String KEY_RIGHT = "right";
public static final String KEY_OWNER = "owner";
public static final String KEY_METADATA = "metadata";
public static final String KEY_NPEID = "npeid";
/**
* Check for owner information in a JSONObject, and return the corresponding PLUSActor. Wherever
* possible, pre-existing actors will be loaded and reused.
* @param obj the JSONObject provided as a web service input.
* @return the PLUSActor object corresponding to the plus:owner field of the object.
* @throws PLUSException
*/
protected static PLUSActor getOwner(JsonObject obj) throws PLUSException {
try {
String name = obj.get("plus:owner").getAsString();
// TODO is this always correct? Should we always create if it can't be found?
if(name != null && !"".equals(name))
return Neo4JPLUSObjectFactory.getActor(name, true);
} catch(Exception exc) { ; }
return Neo4JPLUSObjectFactory.getActor("Unknown", true);
} // End getOwner
protected static HashMap<String,Object> npidNodeToD3(String nodeLabel, NonProvenanceEdge npe) {
HashMap<String,Object> n = new HashMap<String,Object>();
n.put(KEY_ID, nodeLabel);
n.put(KEY_LABEL, nodeLabel);
n.put(KEY_TYPE, "npid");
n.put(KEY_SUBTYPE, "npid");
n.put(KEY_CREATED, npe.getCreated());
return n;
} // End npidNodeToD3
public static Map<String,Object> provenanceObjectToD3(PLUSObject obj) {
// Get the basic storable properties; sub-class specific.
Map<String,Object> n = obj.getStorableProperties();
// Put some D3-specific stuff in there.
n.put(KEY_ID, obj.getId());
n.put(KEY_LABEL, obj.getName());
if(obj.getOwner() != null) n.put("ownerid", obj.getOwner().getId());
PrivilegeSet ps = obj.getPrivileges();
if(ps != null) {
ArrayList<Object> privs = new ArrayList<Object>();
for(PrivilegeClass pc : ps.getPrivilegeSet()) { privs.add(pc.toString()); }
n.put("privileges", privs);
} // End if
if(obj.getSourceHints() != null) n.put("sourceHints", obj.getSourceHints().toString());
// Metadata is handled separately/specially.
HashMap<Object,Object> mData = new HashMap<Object,Object>();
Metadata m = obj.getMetadata();
for(Object k : m.keySet()) mData.put("" + k, m.get(k));
n.put(KEY_METADATA, mData);
return n;
} // End provenanceObjectToD3
/**
* This creates a JSON string suitable to represent a given provenance collection for D3.
* It broadly follows the format found at http://bl.ocks.org/mbostock/4062045#miserables.json
* @param col a provenance collection
* @return a JSON string
* @throws PLUSException
*/
public static String provenanceCollectionToD3Json(ProvenanceCollection col) {
HashMap<String,Object> structure = new HashMap<String,Object>();
ArrayList<Object> nodes = new ArrayList<Object>();
ArrayList<Object> links = new ArrayList<Object>();
ArrayList<Object> actors = new ArrayList<Object>();
HashMap<String,Integer> indexMapping = new HashMap<String,Integer>();
int idx=0;
if(col == null) {
log.severe("Cannot convert NULL collection to JSON");
return null;
}
// Always sort by creation date; sometimes collections will be serialized for
// feeds.
for(PLUSObject obj : col.getNodesInOrderedList(ProvenanceCollection.SORT_BY_CREATION)) {
// Not all collections add actors separately; but for the JSON serialization this must be done.
// If a given object has an owner, that owner should appear in the actor section, otherwise
// when it comes time to deserialize the document, the owner can't be recreated from just the ID
// that appears as a node property.
if(obj.getOwner() != null) col.addActor(obj.getOwner(), false);
nodes.add(provenanceObjectToD3(obj));
indexMapping.put(obj.getId(), idx);
idx++;
}
for(NonProvenanceEdge npe : col.getNonProvenanceEdges()) {
String oid = npe.getId();
String id1 = npe.getFrom();
String id2 = npe.getTo();
String type = npe.getType();
Integer fromIdx = indexMapping.get(id1);
Integer toIdx = indexMapping.get(id2);
/*
if(PLUSUtils.isPLUSOID(id1) && PLUSUtils.isPLUSOID(id2)) {
System.err.println("Processing NPE " + npe + " FROM " + id1 + " => " + id2 + " fromIDX=" + fromIdx + " toIDX=" + toIdx);
System.err.println("COL contains from: " + col.containsObjectID(id1));
System.err.println("COL contains to: " + col.containsObjectID(id2));
}
*/
if(fromIdx == null) {
indexMapping.put(id1, idx);
nodes.add(npidNodeToD3(id1, npe));
fromIdx = idx;
idx++;
}
if(toIdx == null) {
indexMapping.put(id2, idx);
nodes.add(npidNodeToD3(id2, npe));
toIdx = idx;
idx++;
}
HashMap<String,Object> jsonEdge = new HashMap<String,Object>();
jsonEdge.put(KEY_NPEID, oid);
jsonEdge.put(KEY_SOURCE, fromIdx);
jsonEdge.put(KEY_TARGET, toIdx);
jsonEdge.put(KEY_FROM, id1);
jsonEdge.put(KEY_TO, id2);
jsonEdge.put(KEY_LABEL, type);
jsonEdge.put(KEY_TYPE, "npe");
jsonEdge.put(KEY_LEFT, new Boolean(false));
jsonEdge.put(KEY_RIGHT, new Boolean(true));
jsonEdge.put(KEY_CREATED, npe.getCreated());
if(npe.getSourceHints() != null) jsonEdge.put("sourceHints", npe.getSourceHints().toString());
links.add(jsonEdge);
} // End foreach NPE
for(PLUSEdge e : col.getEdges()) {
Integer fromIdx = indexMapping.get(e.getFrom().getId());
Integer toIdx = indexMapping.get(e.getTo().getId());
if(fromIdx == null || toIdx == null) {
// log.warning("Missing idx for either " + e.getFrom() + " or " + e.getTo());
// Do not report this edge. This usually occurs when the edge points to a
// valid node that is outside of this provenance collection. E.g. if you
// load a 50-node graph, and the graph actually has 51 nodes, there might be
// an edge to that 51st node where that node isn't in the collection, but
// the edge is valid.
continue;
} // End if
HashMap<String,Object> jsonEdge = new HashMap<String,Object>();
jsonEdge.put(KEY_SOURCE, fromIdx);
jsonEdge.put(KEY_TARGET, toIdx);
jsonEdge.put(KEY_FROM, e.getFrom().getId());
jsonEdge.put(KEY_TO, e.getTo().getId());
jsonEdge.put(KEY_LEFT, new Boolean(false));
jsonEdge.put(KEY_RIGHT, new Boolean(true));
jsonEdge.put(KEY_LABEL, e.getType());
jsonEdge.put(KEY_TYPE, e.getType());
if(e.getSourceHints() != null) jsonEdge.put("sourceHints", e.getSourceHints().toString());
jsonEdge.put(KEY_WORKFLOW, e.getWorkflow().getId());
links.add(jsonEdge);
} // End foreach PLUSEdge
structure.put("nodes", nodes);
structure.put("links", links);
HashMap<String,Object> actorProps = null;
for(PLUSActor a : col.getActors()) {
actorProps = new HashMap<String,Object>();
actorProps.put(KEY_ID, a.getId());
actorProps.put(KEY_NAME, a.getName());
actorProps.put("created", a.getCreated());
actorProps.put(KEY_TYPE, a.getType());
actors.add(actorProps);
}
structure.put("actors", actors);
HashMap<String,Object> nodeTags = new HashMap<String,Object>();
for(String oid : col.getTaggedNodes()) {
HashMap<String,String> tags = col.getTags(oid);
nodeTags.put(oid, tags);
}
structure.put("nodeTags", nodeTags);
if(col instanceof LineageDAG) {
LineageDAG dag = (LineageDAG)col;
FingerPrint fp = dag.getFingerPrint();
structure.put("fingerprint", fp.getStorableProperties());
}
return new GsonBuilder().setPrettyPrinting().create().toJson(structure);
}
public static String provenanceCollectionToJITJson(ProvenanceCollection col) throws PLUSException {
List<Object> structure = new ArrayList<Object>();
for(PLUSObject obj : col.getNodes()) {
HashMap<String,Object> h = new HashMap<String,Object>();
h.put(KEY_ID, obj.getId());
h.put(KEY_NAME, obj.getName());
HashMap<Object,Object> data = new HashMap<Object,Object>();
data.put("$type", "triangle");
if(obj.isDataItem()) data.put("$type", "ellipse");
else if(obj.isInvocation()) data.put("$type", "rectangle");
Metadata m = obj.getMetadata();
for(Object k : m.keySet()) data.put("metadata:" + k, m.get(k));
Map<String,Object> sprops = obj.getStorableProperties();
for(String k : sprops.keySet()) {
if(sprops.get(k) instanceof String) data.put(k, sprops.get(k));
}
if(obj.getOwner() != null) data.put("owner", obj.getOwner().getName());
data.put("created", obj.getCreatedAsDate().toString());
h.put("data", data);
List<Object> adjs = new ArrayList<Object>();
for(PLUSEdge e : col.getOutboundEdgesByNode(obj.getId())) {
HashMap<String,Object> adj = new HashMap<String,Object>();
adj.put("nodeTo", e.getTo());
adj.put("nodeFrom", e.getFrom());
HashMap<String,String> edgeData = new HashMap<String,String>();
edgeData.put(KEY_LABEL, e.getType());
edgeData.put(KEY_WORKFLOW, e.getWorkflow().getId());
adj.put("data", edgeData);
adjs.add(adj);
}
h.put("adjacencies", adjs);
structure.add(h);
} // End for
return new GsonBuilder().setPrettyPrinting().create().toJson(structure);
}
} // End JSONConverter