/*
* "Copyright (c) 2010-12 The Regents of the University of California.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice, the following
* two paragraphs and the author appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
*
* Author: Jorge Ortiz (jortiz@cs.berkeley.edu)
* StreamFS release version 2.2
*/
package local.metadata.context;
import local.db.*;
import local.rest.*;
import local.rest.resources.*;
import local.rest.resources.util.*;
import net.sf.json.*;
import jdsl.graph.api.*;
import jdsl.graph.ref.*;
import jdsl.graph.algo.*;
import jdsl.core.api.ObjectIterator;
//import com.sun.net.httpserver.*;
//import java.util.concurrent.Executors;
import java.util.*;
//import java.io.*;
//import java.net.*;
import java.util.logging.Logger;
import java.util.logging.Level;
public class MetadataGraph{
private static MetadataGraph metadataGraph = null;
private Hashtable<String, Vertex> pubNodes = null;
private Hashtable<String, Vertex> nonpubNodes = null;
private Hashtable<String, Vertex> symlinkNodes = null;
private Hashtable<String, Vertex> externalNodes = null;
private IncidenceListGraph internalGraph = null;
protected static transient final Logger logger = Logger.getLogger(MetadataGraph.class.getPackage().getName());
protected static MySqlDriver database = (MySqlDriver) DBAbstractionLayer.database;
private MetadataGraph(){
internalGraph = new IncidenceListGraph();
pubNodes = new Hashtable<String, Vertex>();
nonpubNodes = new Hashtable<String, Vertex>();
externalNodes = new Hashtable<String, Vertex>();
symlinkNodes = new Hashtable<String, Vertex>();
populateInternalGraph();
}
public static MetadataGraph getInstance(){
if(metadataGraph == null)
metadataGraph = new MetadataGraph();
return metadataGraph;
}
private synchronized void populateInternalGraph(){
JSONArray hardlinks = database.getAllHardLinks();
for(int i=0; i<hardlinks.size(); i++){
String thisPath = (String)hardlinks.get(i);
Resource thisResource = RESTServer.getResource(thisPath);
if(thisResource !=null){
thisPath = thisResource.getURI();
Vertex v = internalGraph.insertVertex(thisPath);
v.set("path", thisPath);
if(RESTServer.getResource(thisPath).getType()==ResourceUtils.PUBLISHER_RSRC ||
RESTServer.getResource(thisPath).getType()==ResourceUtils.GENERIC_PUBLISHER_RSRC)
pubNodes.put(thisPath,v);
else
nonpubNodes.put(thisPath, v);
}
}
//create the links between each of the nodes by adding an edge from parent
//to the node
for(int i=0; i<hardlinks.size(); i++){
String thisPath = (String)hardlinks.get(i);
Vertex thisNode = getVertex(thisPath);
if(!thisPath.equals("/")){
String parentPath = getParentPath(thisPath);
Vertex parentNode = getVertex(parentPath);
if(parentNode != null && thisNode != null)
internalGraph.insertDirectedEdge(parentNode, thisNode, "hardlink");
}
}
//handle the symlinks by walking the path from a source vertex to a destination vertex
JSONArray symlinks = database.getAllSymlinks();
for(int i=0; i<symlinks.size(); i++){
String thisPath = (String)symlinks.get(i);
Resource r = RESTServer.getResource(thisPath);
if(r!=null){
thisPath = RESTServer.getResource(thisPath).getURI();
Vertex v = internalGraph.insertVertex(thisPath);
v.set("path", thisPath);
symlinkNodes.put(thisPath, v);
}
}
//add nodes to the graph that are external symlinks to remote instance of streamfs
JSONArray externalLinks = database.getAllExternalLinks();
for(int i=0; i<externalLinks.size(); i++){
String thisPath = (String)externalLinks.get(i);
thisPath = RESTServer.getResource(thisPath).getURI();
Vertex v = internalGraph.insertVertex(thisPath);
v.set("path", thisPath);
externalNodes.put(thisPath, v);
}
//create the links between the symlinks, their parent, and the vertex they link to
for(int i=0; i<symlinks.size(); i++){
//create a link between this symlink and the resource it points to
SymlinkResource thisResource = (SymlinkResource)RESTServer.getResource((String)symlinks.get(i));
if(thisResource !=null){
String thisPath = thisResource.getURI();
String linksToPath = thisResource.getLinkString();
Vertex symlinkNode = getVertex(thisPath);
if(linksToPath.startsWith("/")){
Vertex linksToNode =getVertex(linksToPath);
internalGraph.insertDirectedEdge(symlinkNode, linksToNode, "linksto");
}
//create a link between this resource and its parent
String symlinkParentPath = getParentPath((String)symlinks.get(i));
symlinkParentPath = RESTServer.getResource(symlinkParentPath).getURI();
Vertex parentVertex = getVertex(symlinkParentPath);
internalGraph.insertDirectedEdge(parentVertex, symlinkNode, "symlink");
} else {
logger.fine("Could not get: " + (String)symlinks.get(i));
}
}
}
/**
* Looks for the vertex object based on path and returns it.
*/
private Vertex getVertex(String path){
Resource thisResource = RESTServer.getResource(path);
if(thisResource !=null){
path = RESTServer.getResource(path).getURI();
Vertex vertex = nonpubNodes.get(path);
if(vertex==null)
vertex = pubNodes.get(path);
if(vertex ==null)
vertex = symlinkNodes.get(path);
if(vertex ==null)
vertex = externalNodes.get(path);
return vertex;
}
return null;
}
/**
* Looks for the vertex object by path and removed it from the associated list.
*/
private boolean removeVertex(String path){
path = RESTServer.getResource(path).getURI();
if(path!=null){
nonpubNodes.remove(path);
pubNodes.remove(path);
symlinkNodes.remove(path);
externalNodes.remove(path);
return true;
}
return false;
}
/**
* Parses the path and returns the path fo the parent.
*/
private String getParentPath(String path){
Resource resource = RESTServer.getResource(path);
if(resource == null)
return null;
path = RESTServer.getResource(path).getURI();
if(path.equals("/"))
return null;
StringTokenizer tokenizer = new StringTokenizer(path, "/");
Vector<String> elts = new Vector<String>();
while(tokenizer.hasMoreElements())
elts.add(tokenizer.nextToken());
StringBuffer parentPathBuf = new StringBuffer();
for(int k=0; k<elts.size()-1; k++)
parentPathBuf.append("/").append(elts.elementAt(k));
parentPathBuf.append("/");
return parentPathBuf.toString();
}
public synchronized boolean addNode(String resourcePath){
logger.info("Attempting to add: " + resourcePath);
if(resourcePath !=null){
Resource resource = RESTServer.getResource(resourcePath);
Vertex thisVertex = null;
boolean symlink=false;
if((thisVertex =internalGraph.insertVertex(resourcePath)) !=null){
thisVertex.set("path", resourcePath);
String linksToStr = null; //only used if this vertex is a symlink
if(resource.getType() == ResourceUtils.DEFAULT_RSRC ||
resource.getType() == ResourceUtils.GENERIC_PUBLISHER_RSRC){
pubNodes.put(resource.getURI(), thisVertex);
} else if(resource.getType() == ResourceUtils.SYMLINK_RSRC){
symlinkNodes.put(resource.getURI(), thisVertex);
linksToStr = ((SymlinkResource)resource).getLinkString();
Vertex linksToNode = null;
if(linksToStr.startsWith("http://")){
Vertex node= getVertex(linksToStr);
if(node ==null){
node = internalGraph.insertVertex(linksToStr);
node.set("path", linksToStr);
}
externalNodes.put(linksToStr, node);
} else {
linksToNode = getVertex(linksToStr);
}
internalGraph.insertDirectedEdge(thisVertex, linksToNode, "linksto");
symlink=true;
} else {
nonpubNodes.put(resource.getURI(), thisVertex);
String parent = getParentPath(resource.getURI());
Vertex parentVertex = getVertex(parent);
}
String parent = getParentPath(resource.getURI());
Vertex parentVertex = getVertex(parent);
if(symlink){
internalGraph.insertDirectedEdge(parentVertex, thisVertex, "symlink");
if(hasCycle()){
//there is a cycle introduced by this symlink, backtrack the change and return false
removeNode((String)thisVertex.get("path"));
Vertex linksToVertex = getVertex(linksToStr);
removeNode((String)linksToVertex.get("path"));
return false;
} else {
logger.fine("NO CYCLE DETECTED");
}
} else{
internalGraph.insertDirectedEdge(parentVertex, thisVertex, "hardlink");
}
return true;
}
}
return false;
}
public synchronized boolean removeNode(String resourcePath){
Vertex thisVertex = getVertex(resourcePath);
if(thisVertex!=null){
internalGraph.removeVertex(thisVertex);
removeVertex(resourcePath);
}
return false;
}
/**
* Checks if there is a cycle in the graph
*/
public boolean hasCycle(){
DirectedFindCycleDFS dfs = new DirectedFindCycleDFS();
Vertex root= getVertex("/");
dfs.execute(internalGraph, root);
ObjectIterator iterator = dfs.getCycle();
if(iterator.hasNext())
return true;
return false;
}
/**
* Check if there exists a path between the source resource and the destination resource is the resource/context graph.
* @param srcResource the path of the source resource.
* @param dstResource the path of the destination resource.
* @return an array of arrays, where each element is a two-element array that consists of the src vertex and destination
* vertex for an edge on the path described by the array returned. Null if no path exists.
*/
public JSONArray pathExists(String srcResourcePath, String dstResourcePath){
Vertex srcVertex = getVertex(srcResourcePath);
Vertex dstVertex = getVertex(dstResourcePath);
JSONArray pathElts = null;
if(srcVertex!=null && dstVertex!=null){
PathFinder pathFinder =new PathFinder();
pathFinder.execute(internalGraph, srcVertex, dstVertex);
if(pathFinder.pathExists()){
pathElts = new JSONArray();
EdgeIterator edgesInPath = pathFinder.reportPath();
while(edgesInPath.hasNext()){
Edge e = edgesInPath.nextEdge();
JSONArray thisEdge = new JSONArray();
Vertex[] vertices = internalGraph.endVertices(e);
thisEdge.add(0, vertices[0].get("path"));
thisEdge.add(1, vertices[1].get("path"));
pathElts.add(thisEdge);
}
}
}
return pathElts;
}
/**
* Used to find paths in the graph.
*/
public class PathFinder extends IntegerDijkstraPathfinder{
public PathFinder(){
super();
}
protected int weight(Edge e){
return 1;
}
}
}