Package ca.eandb.jdcp.server.classmanager

Source Code of ca.eandb.jdcp.server.classmanager.DbClassManager$DbChildClassManager

/*
* Copyright (c) 2008 Bradley W. Kimmel
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

package ca.eandb.jdcp.server.classmanager;

import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import javax.sql.DataSource;

import org.apache.log4j.Logger;

import ca.eandb.util.UnexpectedException;
import ca.eandb.util.sql.DbUtil;

/**
* @author Brad Kimmel
*
*/
public final class DbClassManager extends AbstractClassManager implements ParentClassManager {

  private static final Logger logger = Logger.getLogger(DbClassManager.class);

  private static final Random rnd = new Random();

  private final DataSource ds;

  private final Map<Integer, DbChildClassManager> children = new HashMap<Integer, DbChildClassManager>();

  private int snapshotIndex = -1;

  public DbClassManager(DataSource ds) {
    this.ds = ds;
  }

  public void prepareDataSource() throws SQLException {
    Connection con = null;
    String sql;
    try {
      con = ds.getConnection();
      con.setAutoCommit(false);

      DatabaseMetaData meta = con.getMetaData();
      ResultSet rs = meta.getTables(null, null, null, new String[]{"TABLE"});
      int tableNameColumn = rs.findColumn("TABLE_NAME");
      int count = 0;
      while (rs.next()) {
        String tableName = rs.getString(tableNameColumn);
        if (tableName.equalsIgnoreCase("ParentClasses") ||
          tableName.equalsIgnoreCase("ChildClasses") ||
          tableName.equalsIgnoreCase("ChildClassManagers")) {
          count++;
        }
      }

      if (count == 0) {
        String intType = DbUtil.getTypeName(Types.INTEGER, con);
        String blobType = DbUtil.getTypeName(Types.BLOB, con);
        String nameType = DbUtil.getTypeName(Types.VARCHAR, 1024, con);
        String md5Type = DbUtil.getTypeName(Types.BINARY, 16, con);

        sql =  "CREATE TABLE ParentClasses ( \n" +
            "  Name " + nameType + " NOT NULL, \n" +
            "  SnapshotIndex " + intType + " NOT NULL, \n" +
            "  Definition " + blobType + " NOT NULL, \n" +
            "  MD5 " + md5Type + " NOT NULL, \n" +
            "  PRIMARY KEY (Name, SnapshotIndex) \n" +
            ")";
        DbUtil.update(ds, sql);

        sql =  "CREATE TABLE ChildClassManagers ( \n" +
            "  ChildID " + intType + " NOT NULL PRIMARY KEY, \n" +
            "  SnapshotIndex " + intType + " NOT NULL \n" +
            ")";
        DbUtil.update(ds, sql);

        sql =  "CREATE TABLE ChildClasses ( \n" +
            "  ChildID " + intType + " NOT NULL, \n" +
            "  Name " + nameType + " NOT NULL, \n" +
            "  Definition " + blobType + " NOT NULL, \n" +
            "  MD5 " + md5Type + " NOT NULL, \n" +
            "  PRIMARY KEY (ChildID, Name), \n" +
            "  FOREIGN KEY (ChildID) REFERENCES ChildClassManagers(ChildID) \n" +
            ")";
        DbUtil.update(ds, sql);
        con.commit();
      }

      con.setAutoCommit(true);
    } catch (SQLException e) {
      DbUtil.rollback(con);
      throw e;
    } finally {
      DbUtil.close(con);
    }
  }

  private int getSnapshotIndex(Connection con) throws SQLException {
    if (snapshotIndex < 0) {
      snapshotIndex = 1 + DbUtil.queryInt(con, -1,
          "SELECT MAX(SnapshotIndex) " +
          "FROM ChildClassManagers");
    }
    return snapshotIndex;
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ParentClassManager#createChildClassManager()
   */
  public ChildClassManager createChildClassManager() {
    Connection con = null;

    try {
      con = ds.getConnection();
      con.setAutoCommit(false);

      String sql =
          "SELECT COUNT(1) " +
          "FROM ChildClassManagers " +
          "WHERE ChildID = ?";

      int id;
      do {
        id = rnd.nextInt();
      } while (DbUtil.queryInt(con, 0, sql, id) > 0);

      int snapshot = getSnapshotIndex(con);
      DbUtil.update(con,
          "INSERT INTO ChildClassManagers " +
          "  (ChildID, SnapshotIndex) " +
          "VALUES (?, ?)",
          id, snapshot);

      con.commit();
      con.setAutoCommit(true);

      DbChildClassManager child = new DbChildClassManager(id);
      children.put(id, child);
      return child;

    } catch (SQLException e) {
      DbUtil.rollback(con);
      logger.error("Error communicating with class manager database.", e);
      throw new RuntimeException(e);
    } finally {
      DbUtil.close(con);
    }
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ParentClassManager#getChildClassManager(int)
   */
  public ChildClassManager getChildClassManager(int id) {
    synchronized (children) {
      DbChildClassManager child = children.get(id);

      if (child == null) {
        try {
          String sql =
              "SELECT COUNT(1) " +
              "FROM ChildClassManagers " +
              "WHERE ChildID = ?";

          if (DbUtil.queryInt(ds, 0, sql, id) > 0) {
            child = new DbChildClassManager(id);
            children.put(id, child);
          }
        } catch (SQLException e) {
          logger.error("An error occurred while attempting to restore a child class manager from the database.", e);
        }
      }

      return child;
    }
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ClassManager#getClassDigest(java.lang.String)
   */
  public byte[] getClassDigest(String name) {
    return getClassField(name, "MD5");
  }

  /* (non-Javadoc)
   * @see ca.eandb.util.classloader.ClassLoaderStrategy#getClassDefinition(java.lang.String)
   */
  public ByteBuffer getClassDefinition(String name) {
    byte[] def = getClassField(name, "Definition");
    return (def != null) ? ByteBuffer.wrap(def) : null;
  }

  private byte[] getClassField(String name, String field) {
    try {
      return DbUtil.queryBinary(ds, null,
          "SELECT " + field + " " +
          "FROM ParentClasses " +
          "WHERE Name = ? " +
          "ORDER BY SnapshotIndex DESC",
          name);
    } catch (SQLException e) {
      logger.error("Could not retrieve class definition from database.", e);
      throw new RuntimeException(e);
    }
  }

  /* (non-Javadoc)
   * @see ca.eandb.jdcp.server.classmanager.ClassManager#setClassDefinition(java.lang.String, java.nio.ByteBuffer)
   */
  public void setClassDefinition(String name, ByteBuffer def) {
    Connection con = null;
    try {
      con = ds.getConnection();
      con.setAutoCommit(false);

      int snapshot = getSnapshotIndex(con);

      byte[] bytes = new byte[def.remaining()];
      def.get(bytes);

      MessageDigest alg;
      try {
        alg = MessageDigest.getInstance("MD5");
      } catch (NoSuchAlgorithmException e) {
        DbUtil.rollback(con);
        throw new UnexpectedException(e);
      }
      byte[] digest = alg.digest(bytes);

      String sql =
          "SELECT COUNT(1) " +
          "FROM ParentClasses " +
          "WHERE Name = ? " +
          "  AND SnapshotIndex = ?";
      if (DbUtil.queryInt(con, 0, sql, name, snapshot) > 0) {
        DbUtil.update(con,
            "UPDATE ParentClasses " +
            "SET " +
            "  Definition = ?, " +
            "  MD5 = ? " +
            "WHERE Name = ? " +
            "  AND SnapshotIndex = ?",
            bytes, digest, name, snapshot);
      } else {
        DbUtil.update(con,
            "INSERT INTO ParentClasses " +
            "  (SnapshotIndex, Name, Definition, MD5) " +
            "VALUES (?, ?, ?, ?)",
            snapshot, name, bytes, digest);
      }

      con.commit();
      con.setAutoCommit(true);
    } catch (SQLException e) {
      DbUtil.rollback(con);
      logger.error("Unable to persist class definition to database.", e);
      throw new RuntimeException(e);
    } finally {
      DbUtil.close(con);
    }
  }

  /**
   * @author Brad Kimmel
   *
   */
  private final class DbChildClassManager extends AbstractClassManager
      implements ChildClassManager {

    private final int id;

    private boolean released = false;

    public DbChildClassManager(int id) {
      this.id = id;
    }

    private void check() {
      if (released) {
        throw new IllegalStateException();
      }
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ChildClassManager#getChildId()
     */
    public int getChildId() {
      check();
      return id;
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ClassManager#setClassDefinition(java.lang.String, java.nio.ByteBuffer)
     */
    public void setClassDefinition(String name, ByteBuffer def) {
      check();

      Connection con = null;
      try {
        con = ds.getConnection();
        con.setAutoCommit(false);

        byte[] bytes = new byte[def.remaining()];
        def.get(bytes);

        MessageDigest alg;
        try {
          alg = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
          DbUtil.rollback(con);
          throw new UnexpectedException(e);
        }
        byte[] digest = alg.digest(bytes);

        String sql =
            "SELECT COUNT(1) " +
            "FROM ChildClasses " +
            "WHERE ChildID = ? " +
            "  AND Name = ?";
        if (DbUtil.queryInt(con, 0, sql, id, name) > 0) {
          DbUtil.update(con,
              "UPDATE ChildClasses " +
              "SET " +
              "  Definition = ?, " +
              "  MD5 = ? " +
              "WHERE ChildID = ? " +
              "  AND Name = ?",
              bytes, digest, id, name);
        } else {
          DbUtil.update(con,
              "INSERT INTO ChildClasses " +
              "  (ChildID, Name, Definition, MD5) " +
              "VALUES (?, ?, ?, ?)",
              id, name, bytes, digest);
        }

        con.commit();
        con.setAutoCommit(true);
      } catch (SQLException e) {
        DbUtil.rollback(con);
        logger.error("Unable to persist class definition to database.", e);
        throw new RuntimeException(e);
      } finally {
        DbUtil.close(con);
      }
    }

    /* (non-Javadoc)
     * @see ca.eandb.util.classloader.ClassLoaderStrategy#getClassDefinition(java.lang.String)
     */
    public ByteBuffer getClassDefinition(String name) {
      byte[] def = getClassField(name, "Definition");
      return (def != null) ? ByteBuffer.wrap(def) : null;
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ClassManager#getClassDigest(java.lang.String)
     */
    public byte[] getClassDigest(String name) {
      return getClassField(name, "MD5");
    }

    private byte[] getClassField(String name, String field) {
      check();

      try {
        byte[] data = DbUtil.queryBinary(ds, null,
            "SELECT " + field + " " +
            "FROM ChildClasses " +
            "WHERE ChildID = ? " +
            "  AND Name = ?",
            id, name);
        if (data != null) {
          return data;
        }

        return DbUtil.queryBinary(ds, null,
            "SELECT " + field + " " +
            "FROM ChildClassManagers, ParentClasses " +
            "WHERE ChildClassManagers.ChildID = ? " +
            "  AND ParentClasses.SnapshotIndex <= ChildClassManagers.SnapshotIndex " +
            "  AND ParentClasses.Name = ? " +
            "ORDER BY ParentClasses.SnapshotIndex DESC",
            id, name);
      } catch (SQLException e) {
        logger.error("Could not retrieve class digest from database.", e);
        throw new RuntimeException(e);
      }
    }

    /* (non-Javadoc)
     * @see ca.eandb.jdcp.server.classmanager.ChildClassManager#release()
     */
    public void release() {
      Connection con = null;
      try {
        con = ds.getConnection();
        con.setAutoCommit(false);

        DbUtil.update(con,
            "DELETE FROM ChildClasses " +
            "WHERE ChildID = ?",
            id);

        DbUtil.update(con,
            "DELETE FROM ChildClassManagers " +
            "WHERE ChildID = ?",
            id);

        con.commit();
        con.setAutoCommit(true);
        released = true;
      } catch (SQLException e) {
        DbUtil.rollback(con);
        logger.error("Failed to remove child class manager from database.", e);
      } finally {
        DbUtil.close(con);
      }
    }

  }

}
TOP

Related Classes of ca.eandb.jdcp.server.classmanager.DbClassManager$DbChildClassManager

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.