package org.iplantc.phyloviewer.viewer.server;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.sql.DataSource;
import org.iplantc.phyloviewer.shared.model.Tree;
import org.iplantc.phyloviewer.viewer.client.model.RemoteNode;
import org.iplantc.phyloviewer.viewer.server.db.ConnectionUtil;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class DatabaseTreeData implements ITreeData
{
public static final int SUBTREE_QUERY_THRESHOLD = 4; //recalculated for NCBI tree on postgres database
private DataSource pool;
public DatabaseTreeData(DataSource pool) {
this.pool = pool;
}
@Override
public RemoteNode getSubtree(int rootID, int depth)
{
if (depth >= SUBTREE_QUERY_THRESHOLD)
{
return getSubtreeInTwoQueries(rootID, depth);
}
else
{
return getSubtreeRecursive(rootID, depth);
}
}
public RemoteNode getSubtreeRecursive(int rootID, int depth)
{
RemoteNode node = null;
Connection conn = null;
PreparedStatement rootNodeStmt = null;
ResultSet rs = null;
try
{
conn = pool.getConnection();
rootNodeStmt = conn.prepareStatement("select * from node natural join topology where node.node_id = ?");
rootNodeStmt.setInt(1, rootID);
rs = rootNodeStmt.executeQuery();
if (rs.next())
{
node = createNode(rs,pool,true);
if (depth > 0 && node.getNumberOfChildren() > 0) {
RemoteNode[] children = getChildren(node.getId(), depth - 1, conn);
node.setChildren(children);
}
}
}
catch(SQLException e)
{
e.printStackTrace();
}
finally
{
ConnectionUtil.close(rootNodeStmt);
ConnectionUtil.close(rs);
ConnectionUtil.close(conn);
}
return node;
}
public RemoteNode getSubtreeInTwoQueries(int rootID, int depth)
{
RemoteNode subtree = null;
Connection conn = null;
PreparedStatement getRoot = null;
PreparedStatement getSubtree = null;
ResultSet rootRS = null;
ResultSet subtreeRS = null;
try
{
//using some extra topology metadata to get the entire subtree in two queries instead of recursively querying for children
conn = pool.getConnection();
String sql = "select * from topology where node_id = ?";
getRoot = conn.prepareStatement(sql);
getRoot.setInt(1, rootID);
rootRS = getRoot.executeQuery();
if (rootRS.next()) {
int maxDepth = rootRS.getInt("Depth") + depth;
sql = "select * " +
" from node natural join topology " +
" where LeftNode >= ? and RightNode <= ? and Depth <= ? and tree_id = ?" +
" order by Depth desc ";
getSubtree = conn.prepareStatement(sql);
getSubtree.setInt(1, rootRS.getInt("LeftNode"));
getSubtree.setInt(2, rootRS.getInt("RightNode"));
getSubtree.setInt(3, maxDepth);
getSubtree.setInt(4, rootRS.getInt("tree_id"));
subtreeRS = getSubtree.executeQuery();
subtree = buildTree(subtreeRS);
}
}
catch(SQLException e)
{
//Auto-generated catch block
e.printStackTrace();
}
finally
{
ConnectionUtil.close(rootRS);
ConnectionUtil.close(subtreeRS);
ConnectionUtil.close(getRoot);
ConnectionUtil.close(getSubtree);
ConnectionUtil.close(conn);
}
return subtree;
}
@Override
public RemoteNode[] getChildren(int parentID)
{
RemoteNode[] children = null;
Connection conn = null;
try
{
conn = pool.getConnection();
int depth = 0;
children = getChildren(parentID, depth, conn);
}
catch(SQLException e)
{
e.printStackTrace();
}
finally
{
ConnectionUtil.close(conn);
}
return children;
}
public RemoteNode[] getChildren(int parentID, int depth, Connection conn) throws SQLException
{
ArrayList<RemoteNode> children = new ArrayList<RemoteNode>();
String sql = "select * from node natural join topology where parent_id = ?";
PreparedStatement getChildrenStmt = conn.prepareStatement(sql);
getChildrenStmt.setInt(1, parentID);
ResultSet rs = getChildrenStmt.executeQuery();
while (rs.next()) {
RemoteNode child = createNode(rs,pool,true);
if (depth > 0 && child.getNumberOfChildren() > 0)
{
child.setChildren(getChildren(child.getId(), depth - 1, conn));
}
children.add(child);
}
ConnectionUtil.close(rs);
if (children.size() > 0) {
return children.toArray(new RemoteNode[children.size()]);
} else {
return null;
}
}
@Override
public RemoteNode getRootNode(int treeId)
{
Tree tree = this.getTree(treeId,0);
return (RemoteNode) tree.getRootNode();
}
public Tree getTree(int id, int depth)
{
Tree tree = null;
Connection conn = null;
PreparedStatement statement = null;
ResultSet rs = null;
try
{
conn = pool.getConnection();
String sql = "select * from tree where tree_id = ?";
statement = conn.prepareStatement(sql);
statement.setInt(1,id);
rs = statement.executeQuery();
if (rs.next()) {
tree = new Tree();
tree.setId(rs.getInt("tree_id"));
int rootId = rs.getInt("root_id");
RemoteNode node = getSubtree(rootId, depth);
tree.setRootNode(node);
}
}
catch(SQLException e)
{
//Auto-generated catch block
e.printStackTrace();
}
finally
{
ConnectionUtil.close(rs);
ConnectionUtil.close(statement);
ConnectionUtil.close(statement);
ConnectionUtil.close(conn);
}
return tree;
}
/**
* Takes the ResultSet from getSubtree and builds a subtree from it.
*
* The nodes in subtreeRS must be sorted by Depth from largest to smallest.
*/
private RemoteNode buildTree(ResultSet subtreeRS) throws SQLException
{
HashMap<Integer,List<RemoteNode>> childrenLists = new HashMap<Integer,List<RemoteNode>>();
RemoteNode node = null;
while(subtreeRS.next())
{
//create the node
node = createNode(subtreeRS,pool,true);
List<RemoteNode> childrenList = childrenLists.get(node.getId());
if(childrenList != null)
{
RemoteNode[] children = childrenList.toArray(new RemoteNode[childrenList.size()]);
node.setChildren(children);
}
//add the node to its parent's childrenList (creating the list first if it doesn't already exist)
int parentID = subtreeRS.getInt("parent_id");
if(!childrenLists.containsKey(parentID))
{
childrenLists.put(parentID, new ArrayList<RemoteNode>());
}
childrenLists.get(parentID).add(node);
}
return node; //the last row of the resultset was the root node
}
/**
* Creates a RemoteNode from the current row of a ResultSet.
*
* @param rs A ResultSet containing the columns of the node and topology tables. The state of the
* ResultSet (current row, etc) should not be altered by this method
* @throws SQLException
*/
public static RemoteNode createNode(ResultSet rs,DataSource pool,boolean addAltLabel) throws SQLException
{
int id = rs.getInt("node_id");
String label = rs.getString("Label");
int numNodes = rs.getInt("NumNodes");
int numLeaves = rs.getInt("NumLeaves");
int height = rs.getInt("Height");
int depth = rs.getInt("Depth");
int numChildren = rs.getInt("NumChildren");
int leftIndex = rs.getInt("LeftNode");
int rightIndex = rs.getInt("RightNode");
if (addAltLabel && label==null) {
Connection conn = null;
PreparedStatement statement = null;
ResultSet rs2 = null;
conn = pool.getConnection();
String sql = "select * from node_label_lookup where node_id = ?";
statement = conn.prepareStatement(sql);
statement.setInt(1,id);
rs2 = statement.executeQuery();
if (rs2.next()) {
label = rs2.getString("alt_label");
}
ConnectionUtil.close(rs2);
ConnectionUtil.close(statement);
ConnectionUtil.close(conn);
}
return new RemoteNode(id, label, numChildren, numNodes, numLeaves, depth, height, leftIndex, rightIndex);
}
@Override
public String getTrees() {
JSONObject result = new JSONObject();
JSONArray trees = new JSONArray();
Connection conn;
try {
conn = pool.getConnection();
PreparedStatement statement = conn.prepareStatement("select * from tree");
ResultSet rs = statement.executeQuery();
while (rs.next()) {
int uuid = rs.getInt("tree_id");
String name = rs.getString("Name");
trees.put(buildJSONForTree(uuid,name));
}
result.put("trees", trees);
ConnectionUtil.close(statement);
ConnectionUtil.close(conn);
ConnectionUtil.close(rs);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result.toString();
}
private JSONObject buildJSONForTree(int id, String name) throws JSONException {
JSONObject tree = new JSONObject();
tree.put("id", id);
tree.put("name", name);
return tree;
}
}