Package ca.uwaterloo.fydp.db

Source Code of ca.uwaterloo.fydp.db.clientJDBCPipe

package ca.uwaterloo.fydp.db;

import ca.uwaterloo.fydp.ossp.OSSPStimulus;
import ca.uwaterloo.fydp.xcde.*;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.Timestamp;

/**
* The backend for the source replay client.  Implements the XCDEDocClient API to simplify
* integration, but does not accept changes, inbound changes cause a runtime exception.
* @author Andrew Craik
* @version 1.0
*/
public class clientJDBCPipe implements XCDEDocClient, Runnable
  {
  private Connection conn;
  private String mysqlHostname;
  private int mysqlPort;
  private String mysqlDBUser;
  private String mysqlDBPassword;
  private String path;
  private XCDEDocClientCallbacks docClient;
  private Timestamp start, end;
  private volatile Thread resultThread;
  private long separationTime;
  private boolean threadSuspended;
 
  //the default values used for the MySQL connection
  private static final String DEFAULT_MYSQL_SERVER = "localhost";
  private static final int DEFAULT_MYSQL_SERVER_PORT = 3306;
  private static final String DEFAULT_DB_USERNAME = "serverDB";
  private static final String DEFAULT_DB_PASSWORD = "db1user";
 
  /**
   * Create a client to run queries via JDBC on the document history database.
   * This class is designed as the backend for the replay viewer.  It does NOT support
   * receiving changes, only sending the changes out to clients.
   * @param hostname   the fully qualified DNS name of the host running the MySQL server
   * @param port     port to use to connect to the MySQL server on hostname
   * @param dbUser   the username to supply to the MySQL server at connection time
   * @param dbPassword the password to supply to the MySQL server at connection time
   */
  public clientJDBCPipe(String hostname, int port, String dbUser, String dbPassword)
    {
    // we have to load the com.mysql.jdbc.Driver class so that when we go to
    // make an SQL
    // connection we are able to find the specific driver that we need
    try
      {
      Class.forName("com.mysql.jdbc.Driver");
      }
    catch (ClassNotFoundException e)
      {
      throw new RuntimeException("Could not locate the com.mysql.jdbc.Driver class!", e);
      }
   
    //initialize the values, use defaults if no value given
    mysqlHostname = (hostname.equals("")) ? DEFAULT_MYSQL_SERVER : hostname;
    mysqlPort     = (port == 0) ? DEFAULT_MYSQL_SERVER_PORT : port;
    mysqlDBUser   = (dbUser.equals("")) ? DEFAULT_DB_USERNAME : dbUser;
    mysqlDBPassword = (dbPassword.equals("")) ? DEFAULT_DB_PASSWORD : dbPassword;
    separationTime = 500;
    threadSuspended = false;
    }
 
  /**
   * Create a client to run queries via JDBC on the document history database.
   * This class is designed as the backend for the replay viewer.  It does NOT support
   * receiving changes, only sending the changes out to clients.  The no args constructor
   * creates sets all connection parameters to hardcoded defaults.
   *
   * It is recommended that you consider using the 4 arg constructor instead.
   */
  public clientJDBCPipe()
    {
    // we have to load the com.mysql.jdbc.Driver class so that when we go to
    // make an SQL
    // connection we are able to find the specific driver that we need
    try
      {
      Class.forName("com.mysql.jdbc.Driver");
      }
    catch (ClassNotFoundException e)
      {
      throw new RuntimeException("Could not locate the com.mysql.jdbc.Driver class!", e);
      }
   
    //initialize the default values
    mysqlHostname = DEFAULT_MYSQL_SERVER;
    mysqlPort     = DEFAULT_MYSQL_SERVER_PORT;
    mysqlDBUser   = DEFAULT_DB_USERNAME;
    mysqlDBPassword = DEFAULT_DB_PASSWORD;
    separationTime = 500;
    threadSuspended = false;
    }
 
  /**
   * Throws a runtime exception since this XCDEDocClient does not support inbound changes.
   */
  public void makeChange(OSSPStimulus ch)
    {
    throw new RuntimeException("Replay client JDBC pipe cannot accept changes, something isn't written right!");
    }

  /**
   * Attempts to connect to the MySQL server specified by the private fields of the calss.
   * Fields can be set with the 4 arg constructor or the setters provided in this class.<br>
   * <b>Note:</b> Once you have connected you must disconnect when finished to free the MySQL
   * connection.
   * @return true if no exceptions occured setting up the connection
   */
  public boolean connect()
    {
    //check if we are connected, if so return true
    if (conn != null)
      return true;
   
    // now we have the MySQL JDBC dirver loaded we need to connect up using
    // either the user
    // supplied or default values for the different parameters
    try
      {
      conn = DriverManager.getConnection("jdbc:mysql://" + mysqlHostname
          + ":" + mysqlPort + "/xcde", mysqlDBUser, mysqlDBPassword);
      conn.setAutoCommit(true);
      }
    catch (SQLException e)
      {
      System.err.println(e);
      return false;
      }
    return true;
    }
 
  /**
   * Close the open connection to the MySQL server
   */
  public void disconnect()
    {
    //null the thread so it terminates when it wakes up or next iterates
    resultThread = null;
   
    if (conn == null)
      return;
    //shutdown the JDBC connection
    try
      {
      conn.close();
      }
    catch (SQLException e)
      {
      System.err.println(e);
      return;
      }
    conn = null;
    }
 
  /**
   * Called to trigger the download of the initial file contents and start the thread
   * to send the changes sequentially.  Used to load and run the client replay.
   */
  public void beginDownloadState()
    {
    if (!connect())
      throw new RuntimeException("Error connecting with MySQL server, download aborted!");
   
    //now we need to retrieve the file contents at the time closest to our start time
    StringBuffer fileContents = new StringBuffer("");
    Timestamp fileContentStart = null;
    String filename = path.substring(path.lastIndexOf("/")+1);
    String filepath = path.substring(0,path.lastIndexOf("/")+1);
    try
      {
      //first download the newst file contents for the file from the FileContents table
      Statement contentQuery = conn.createStatement();
      String query = "SELECT contents, addDate, fileID FROM FileContents" +
           " WHERE fileID = (SELECT fileID FROM Files INNER JOIN Directories ON Files.DirLoc = Directories.dirID" +
               " WHERE Files.FileName = '" + filename + "' AND Directories.FullName = '" + filepath + "'" +
               " AND (Directories.DropDate < '" + start +"' OR Directories.DropDate IS NULL)" +
               " AND (Files.DropDate < '" + start + "' OR Files.DropDate IS NULL))" +
               " AND addDate < '" + start + "' ORDER BY addDate";
      System.out.println(query);
      ResultSet rs = contentQuery.executeQuery(query);
      rs.last();
      System.out.println(rs.getRow());
      if (rs.getRow() == 0)
        {
        fileContentStart = start;
        }
      else
        {
        fileContentStart = rs.getTimestamp("addDate");
        //append the file contents to our buffer
        fileContents.append(rs.getString("contents"));
        }
      rs.close();
      //now query the change table for changes between the contents and the start time for the replay
      //and apply those changes to the initial state before sending
      rs = contentQuery.executeQuery(buildChangeSetQuery(fileContentStart, start, path));
      rs.last();
      if (rs.getRow() != 0)
        {
        rs.first();
        for (rs.first(); !rs.isAfterLast(); rs.next())
          {
          if (rs.getString("ChangeType").equals("I"))
            {
            fileContents.insert(rs.getInt("ChangePos"), rs.getString("ChangeText"));
            }
          else if (rs.getString("ChangeType").equals("D"))
            {
            fileContents.replace(rs.getInt("ChangePos"), rs.getInt("ChangePos") + Integer.parseInt(rs.getString("ChangeText")), "");
            }
          else
            {
            throw new RuntimeException("Unknown change type in preprocessing the file state");
            }
          }
        }
      }
    catch (SQLException e)
      {
      throw new RuntimeException("SQLException getting the initial file contents for " + path + " ", e);
      }
   
    //send the initial document contents to the client
    docClient.receiveState(this, new XCDEDocument(fileContents.toString()));
   
    //create a thread to send the changes
    resultThread = new Thread(this, "result thread");
    resultThread.start();
    }

  /**
   * Calling this method will cause the stream of incoming changes to pause.  No changes
   * will be sent to the client again until resumePlayback is called.
   */
  public synchronized void pausePlayback()
    {
    threadSuspended = true;
    }
 
  /**
   * Calling this method will cause the stream of incoming changes to resume if the
   * stream was previously paused by pausePlayback.  If the playback is not paused,
   * no change in operation will occur.
   */
  public synchronized void resumePlayback()
    {
    if (!threadSuspended)
      return;
    threadSuspended = false;
    notify();
    }
 
  /**
   * Stops the replay currently in progress permanently.  Once this is called, the
   * position in the replay history if forgotten and beginDownloadState will be need
   * to be called to play the file again.
   */
  public void stopPlayback()
    {
    resultThread = null;
    if (threadSuspended)
      notify();
    }
 
  /**
   * Resets the server configuration after disconnecting from any current
   * session.
   * @param host Hostname of the db server to be connected to
   * @param port The port to connect to the db server on
   * @param user The user name to be used when connecting
   * @param pw The password to be used when connecting
   */
  public void setDBConfig(String host, int port, String user, String pw)
    {
    disconnect();
    mysqlHostname = host;
    mysqlPort = port;
    mysqlDBUser = user;
    mysqlDBPassword = pw;
    }
 
  /**
   * Called to set the object to which change messages will be sent
   */
  public void setCallback(XCDEDocClientCallbacks cb)
    {
    resultThread = null;
    docClient = cb;
    }

  /**
   * Get the full path and file that changes are being sent.
   */
  public String getPath()
    {
    return path;
    }
 
  /**
   * Set the fully qualified name of the file to be sent
   * @param p fully qualified name of the file
   */
  public void setPath(String p)
    {
    resultThread = null;
    path = p;
    }
 
  /**
   * Return the start time for the change window that will be sent to the client
   * @return The timestamp representing the oldest changes that will be sent
   */
  public Timestamp getStartTime()
    {
    return start;
    }
 
  /**
   * Return the end time for the change window that will be sent to the client
   * @return The timestamp representing the cutoff for newest changes that will be sent
   */
  public Timestamp getEndTime()
    {
    return end;
    }
 
  /**
   * Sets the replay start time to s.  Calling this method causes any
   * currently running replay to be terminated.
   * @param s Timestamp representing the start of the replay time window
   */
  public void setStartTime(Timestamp s)
    {
    resultThread = null;
    start = s;
    }
 
  /**
   * Sets the replay end time to e.  This method causes any currently running
   * replay to be terminated.
   * @param e
   */
  public void setEndTime(Timestamp e)
    {
    resultThread = null;
    end = e;
    }
 
  /**
   * Set the time interval to elapse between changes.
   * @param t The time in milliseconds to wait between each change send
   */
  public void setTimeInterval(long t)
    {
    separationTime = t;
    }
 
  /**
   * Get the time interval that is to elapse between the dispatch of changes to the client
   * @return Time in milliseconds between change notifications from the server to the client
   */
  public long getTimeInterval()
    {
    return separationTime;
    }

  /**
   * Build a query to get a set of change events between a start and end date
   * @param s The start time for the change set
   * @param e The end time for the change set
   * @param p The fully qualified name of the file to generate the change set for
   * @return The query to run to generate the set as a string
   */
  private String buildChangeSetQuery(Timestamp s, Timestamp e, String p)
    {
    String fileInnerQuery =
    "(SELECT fileId FROM Files WHERE FileName = '" + p.substring(p.lastIndexOf("/")+1) +
                                    "' AND dirLoc=(SELECT dirID FROM Directories WHERE FullName = '" + p.substring(0,p.lastIndexOf("/")+1)
                                                  + "' AND AddDate < '" + s + "' AND (DropDate > '" + e + "' OR DropDate IS NULL))" +
                                     " AND AddDate < '" + s + "' AND (DropDate > '" + e + "' OR DropDate IS NULL))";
      return "SELECT FileChanges.ChangeType, FileChanges.ChangePos, FileChanges.ChangeText, Users.ShortName FROM FileChanges, Users" +
              " WHERE Users.usersID = FileChanges.UserID AND FileChanges.fileID = " + fileInnerQuery +" AND FileChanges.ChangeTime > '" + s + "' AND FileChanges.ChangeTime < '" + e + "'";
    }
  /**
   * The implementation of the Runnable interface that is used to send the changes at regular
   * intervals to the client.  The thread terminates after the last row is sent to the client
   * or when the thread is nolonger held in the resultThread field (this allows us to shutdown
   * the thread easily).
   */
  public void run()
    {
    Thread thisThread = Thread.currentThread();
    //start sending changes
    ResultSet rs;
    try
      {
      Statement st = conn.createStatement();
      System.out.println(buildChangeSetQuery(start, end, path));
        rs = st.executeQuery(buildChangeSetQuery(start, end, path));
        rs.last();
        if(rs.getRow() == 0)
          {
          rs.close();
          return;
          }
        rs.first();
      }
    catch (SQLException e)
      {
      throw new RuntimeException("error running client viewer query!", e);
      }
       
    try
      {
      //go to first row of result set and iterate to the last calling the docClient at
        //the specified interval
      for (rs.first(); !rs.isAfterLast() && resultThread == thisThread; rs.next())
        {
            //sleep for the required interval
          try
            {
            Thread.sleep(separationTime);
            synchronized(this)
              { 
              while (threadSuspended)
                wait();
              }
            }
          catch (InterruptedException e)
            {
            System.err.println(e);
            }
         
          //build the change message to send to the client
        XCDEDocChange change = null;
         
            if(rs.getString("ChangeType").equals("I"))
              {
              change = new XCDEDocChangeInsertion(rs.getString("ShortName"), rs.getInt("ChangePos"),rs.getString("ChangeText"));
              }
            else if (rs.getString("ChangeType").equals("D"))
              {
            change = new XCDEDocChangeDeletion(rs.getString("ShortName"), rs.getInt("ChangePos"),Integer.parseInt(rs.getString("ChangeText")));
              }
            else
              {
              throw new RuntimeException("Uknown change type in result set: " + rs.getString(0));
              }
            //send the change
            docClient.notifyOfChange(this,change);
        }
      rs.close();
      }
    catch (SQLException e)
      {
      throw new RuntimeException("Error running SQL", e);
      }
    }

  public void pace()
    {
    throw new UnsupportedOperationException("You are not allowed to send changes, don't call pace()!");
    }

  public void syncExec(Runnable task)
    {
    throw new UnsupportedOperationException("You are not allowed to send changes, don't call syncExec()!");
    }

  public void asyncExec(Runnable task)
    {
    throw new UnsupportedOperationException("You are not allowed to send changes, don't call asyncExec()!");
    }

  public boolean isConnected()
    {
    return true;
    }

  public void open()
    {
    beginDownloadState();
    }

  public void close()
    {
    disconnect();   
    }

  public boolean isOpen()
    {
    return true;
    }

  public XCDEDocument getState()
    {
    // TODO Auto-generated method stub
    return null;
    }
  }
TOP

Related Classes of ca.uwaterloo.fydp.db.clientJDBCPipe

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.