Package org.xmlBlaster.util.queue.jdbc

Source Code of org.xmlBlaster.util.queue.jdbc.JdbcConnectionPool

/*------------------------------------------------------------------------------
Name:      JdbcConnectionPool.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
Comment:   JDBC Connection Pool for persistent queues.
Author:    michele@laghi.eu
------------------------------------------------------------------------------*/

package org.xmlBlaster.util.queue.jdbc;

import org.apache.commons.lang.text.StrTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;
import org.xmlBlaster.util.ReplaceVariable;
import org.xmlBlaster.util.ThreadLister;
import org.xmlBlaster.util.XmlBlasterException;
import org.xmlBlaster.util.Global;
import java.util.Properties;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.DatabaseMetaData;
import java.sql.Statement;
import java.util.Hashtable;

import org.xmlBlaster.util.I_Timeout;
import org.xmlBlaster.util.def.ErrorCode;

import org.xmlBlaster.util.queue.I_StorageProblemListener;
import org.xmlBlaster.util.queue.I_StorageProblemNotifier;

import EDU.oswego.cs.dl.util.concurrent.BoundedLinkedQueue;

/**
* A Pool of connections to the database to be used for a persistent queue. To
* keep genericity, queries and update strings are read from properties.
* @see <a href="http://www.xmlBlaster.org/xmlBlaster/doc/requirements/queue.jdbc.commontable.html">The queue.jdbc.commontable requirement</a>
*/
public class JdbcConnectionPool implements I_Timeout, I_StorageProblemNotifier {
   private static String ME = "JdbcConnectionPool";
   private static Logger log = Logger.getLogger(JdbcConnectionPool.class.getName());
   private Global glob = null;
   private BoundedLinkedQueue connections;
  
   /** the initial capacity of this pool. */
   private int capacity;
   private int waitingCalls = 0;
   private long connectionBusyTimeout = 20*60*1000L; /* On high load we wait up to 20 min until xmlBlaster shuts down */
   private int   maxWaitingThreads = 200;
   private Hashtable mapping = null;
   private boolean initialized = false;

   private String tableNamePrefix = "XB"; // stands for "XMLBLASTER", it is chosen short for Postgres max. eval length = 26 chars (timestamp has already 19 chars)
   private String colNamePrefix = "";     // SQLServer does not allow column name 'byteSize' and 'dataId', so we can add a token e.g. XBbyteSize, XBdataId
   private int tableAllocationIncrement = 2;
   /** will be set when a connecton is broken */
   private int status = I_StorageProblemListener.UNDEF;
   private boolean waitingForReentrantConnections = false;
   private String url;
   private String user;
   private String password;
   private long reconnectionTimeout = 10000L;
   private I_StorageProblemListener storageProblemListener = null;

   private static boolean firstConnectError = true;
   private Properties pluginProp = null;
   private boolean dbAdmin = true;
   /** Sets the number of seconds the driver will wait for a
     * <code>Statement</code> object to execute to the given number of seconds.
     * If the limit is exceeded, an <code>SQLException</code> is thrown.
     *
     * @param seconds the new query timeout limit in seconds; zero means
     *        there is no limit
   */
   private int queryTimeout = 0; // wait indefinitely
   private int managerCount = 0;
   private boolean isShutdown = false;
   private boolean enableBatchMode;
   private String configurationIdentifier;
   private boolean cascadeDeleteSupported;
   private boolean nestedBracketsSupported;
   private int forceIsolationLevel = -1;
   private boolean debug;
  
   private final int MIN_POOL_SIZE = 1;

   /**
    * returns the plugin properties, i.e. the specific properties passed to the jdbc queue plugin.
    * These are commonly used by the jdbc manager.
    */
    public Properties getPluginProperties() {
       return this.pluginProp;
    }

  /**
   * Invoked by the timer when a check for reconnection is wanted again
   * @see I_Timeout#timeout(Object)
   */
   public void timeout(Object userData) {
      log.info("Timeout, trying DB reconnect, current index: " + this.connections.size() + ", waiting for reentrant connections: " + this.waitingForReentrantConnections);

      synchronized (this) {
         if (this.waitingForReentrantConnections) {
            if (this.connections.size() == this.capacity)
               this.waitingForReentrantConnections = false;
            else {
               // respan the timer ...
               this.glob.getJdbcConnectionPoolTimer().addTimeoutListener(this, this.reconnectionTimeout, null);
               return; // wait until all connections have been given back to the pool ...
               // in case a connection is blocking this could be blocking everything: find a better way to
               // throw away the blocking connection and avoid to get it back later.
            }
         }
         // try a connection ...
         try {
            if (log.isLoggable(Level.FINE)) log.fine("timeout:retrying to establish connections");
            // initializing and establishing of connections to DB but first clearing the connections ...
            connect(false, false);
         }
         catch (Throwable ex) {
            // clean up the connections which might have been established
            //for (int i = 0; i < this.connections.size(); i++) disconnect(i);
            // respan the timer ...
            disconnect(-1L, true);
            this.glob.getJdbcConnectionPoolTimer().addTimeoutListener(this, this.reconnectionTimeout, null);
         }
      }

   }

   /**
    * Sets the connection listener. Only one is allowed at a time. So if there is
    * already a connection listener, it will be overwritten (and the old one will
    * not get anyu notification anymore).
    */
   synchronized public boolean registerStorageProblemListener(I_StorageProblemListener storageProblemListener) {
      this.storageProblemListener = storageProblemListener;
      return true; // always true
   }

   /**
    * Unregisters the storageProblemListener. If no one has been defined, or
    * if the one you want to unregister is different from the one you have
    * registered, nothing is done and 'false' is returned.
    */
   synchronized public boolean unRegisterStorageProblemListener(I_StorageProblemListener storageProblemListener) {
      if ((this.storageProblemListener == null) || (this.storageProblemListener != storageProblemListener)) return false;
      this.storageProblemListener = null;
      return true;
   }


   /**
    * returns null if no connection available
    */
   private Connection get(long delay) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer("get invoked");
      Connection conn = null;
      if (this.connections.size() > this.capacity)
         throw new XmlBlasterException(this.glob, ErrorCode.INTERNAL_UNKNOWN, ME, "get: Inconsistency in connection index: a negative one is not possible: '" + this.connections.size() + "'");
  
      if (log.isLoggable(Level.FINE)) log.fine("going to retreive a connection");
      try  {
         conn = (Connection)this.connections.poll(delay); // Return and remove an item from channel only if one is available within msecs milliseconds.
         if (conn != null) { // assert code
            setInPool(conn, false);
            /*
            try {
               if (!conn.getAutoCommit()) {
                  log.severe("Get error, expected autoCommit=true but was false" + ThreadLister.getAllStackTraces());
                  conn.setAutoCommit(true);
               }
            }
            catch (Throwable e) {
               log.severe("Get error, expected autoCommit=true but got exception: " + e.toString() + ThreadLister.getAllStackTraces());
            }
            */
         }
      }
      catch (InterruptedException ex) {
         log.warning("the waiting for a connection was interrupted: " + ex.getMessage());
      }
      if (log.isLoggable(Level.FINE)) log.fine("retreived the connection");
      if (conn == null)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME,
            "get: connectionBusyTimeout=" + this.connectionBusyTimeout +
            " occured when waiting for a free DB connection (see xmlBlaster.properties)." +
            " Either the timeout is too short or other connections are blocking, waitingCalls=" +
            this.waitingCalls +
            ", connectionPoolSize=" + this.capacity);
      return conn;            
   }


   private boolean put(Connection conn) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) {
         String warning = "";
         try {
            SQLException warn = conn.getWarnings();
            warning = warn.getMessage();
         }
         catch (Throwable e) {}
         log.finer("put invoked " + warning);
      }
      if (conn == null) return false;

      if (isInPool(conn)) { // assert code
         log.severe("Put error, the returned connection is already in the pool: " + ThreadLister.getAllStackTraces());
         throw new XmlBlasterException(this.glob, ErrorCode.INTERNAL_ILLEGALSTATE, ME,
               "Put error, the returned connection is already in the pool");
      }
      /*
      try {
         if (!conn.getAutoCommit()) { // assert
            log.severe("Put error, expected autoCommit=true but was false" + ThreadLister.getAllStackTraces());
            conn.setAutoCommit(true);
         }
      }
      catch (Throwable e) {
         log.severe("Put error, expected autoCommit=true but got exception: " + e.toString() + ThreadLister.getAllStackTraces());
         throw new XmlBlasterException(this.glob, ErrorCode.INTERNAL_ILLEGALSTATE, ME,
               "Put error, expected autoCommit=true but got exception", e);
      }
      */
      try {
         setInPool(conn, true);
         boolean tmp = this.connections.offer(conn, 5L);
         return tmp;
      }
      catch (InterruptedException ex) {
         log.warning("put: an interruption occured: " + ex.getMessage());
         boolean ret = false;
         // we do this loop since a CTRL-C could cause an interrupted exception
         // and thereby cause the loss of one entry in the connection pool.
         for (int i=0; i < 3; i++) {
            try {
               ret = this.connections.offer(conn, 5L);
               setInPool(conn, true);
               break;
            }
            catch (InterruptedException e) {
               log.warning("put: an interruption occured #" + i + " : " + e.getMessage());
            }
         }
         //if (i >= 3) is logged by calling function
         return ret;
      }
   }


   /**
    * Returns the global object associated with this pool.
    */
   public Global getGlobal() {
      return this.glob;
   }

   /** The default constructor currently does nothing. Initialization is done in the initialize() method. */
   public JdbcConnectionPool() {
      //this.connections = new BoundedLinkedQueue();
   }


   /**
    * returns true if the pool already is initialized, false otherwise.
    */
   public boolean isInitialized() {
      return this.initialized;
   }

   /**
    * Discards the passed connection from the pool and instead adds a new fresh connection to the pool.
    * This method is to be invoked when an exception occured and it can be supposed that the connection
    * is not good anymore.
    *
    * @param conn
    * @throws XmlBlasterException
    */
   public void discardConnection(Connection conn) throws XmlBlasterException {
      if (conn == null)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNKNOWN, "discardConnection", "The connection to discard is null");
      if (log.isLoggable(Level.FINER))
         log.finer("discardConnection " + this.connections.size() + " waiting calls: " + this.waitingCalls);
      try {
         SQLWarning warns = conn.getWarnings();
         if (log.isLoggable(Level.FINE)) {
            while (warns != null) {
               log.fine("errorCode=" + warns.getErrorCode() + " state=" + warns.getSQLState() + ": " + warns.toString().trim());
               warns = warns.getNextWarning();
            }
         }
      }
      catch (Throwable e) {
         log.warning("clearWarnings() failed: " + e.toString());
      }
     
      try {
         conn.close();
      }
      catch (Throwable ex) {
         log.warning("Could not close the connection to be discarded");
      }
      try {
         addConnectionToPool(false);
      }
      catch (SQLException ex) {
         log.warning("Could not close the connection to be discarded");
      }
   }
  

   public static String isolationToString(int isolation) {
      if (isolation == Connection.TRANSACTION_READ_COMMITTED)
         return "TRANSACTION_READ_COMMITTED";
      if (isolation == Connection.TRANSACTION_READ_UNCOMMITTED)
         return "TRANSACTION_READ_UNCOMMITTED";
      if (isolation == Connection.TRANSACTION_REPEATABLE_READ)
         return "TRANSACTION_REPEATABLE_READ";
      if (isolation == Connection.TRANSACTION_SERIALIZABLE)
         return "TRANSACTION_SERIALIZABLE";
      if (isolation == Connection.TRANSACTION_NONE)
         return "TRANSACTION_NONE";
      return "" + isolation;
   }

   public static String getIsolationLevel(Connection conn) {
      try {
         int isolation = conn.getTransactionIsolation();
         return "Supports connection TRANSACTION_READ_COMMITTED-"+Connection.TRANSACTION_READ_COMMITTED+"="
               + conn.getMetaData().supportsTransactionIsolationLevel(
                     Connection.TRANSACTION_READ_COMMITTED)
               + ", TRANSACTION_READ_UNCOMMITTED-"+Connection.TRANSACTION_READ_UNCOMMITTED+"="
               + conn.getMetaData().supportsTransactionIsolationLevel(
                     Connection.TRANSACTION_READ_UNCOMMITTED)
               + ", TRANSACTION_REPEATABLE_READ-"+Connection.TRANSACTION_REPEATABLE_READ+"="
               + conn.getMetaData().supportsTransactionIsolationLevel(
                     Connection.TRANSACTION_REPEATABLE_READ)
               + ", TRANSACTION_SERIALIZABLE-"+Connection.TRANSACTION_SERIALIZABLE+"="
               + conn.getMetaData().supportsTransactionIsolationLevel(
                     Connection.TRANSACTION_SERIALIZABLE)
               + ". Using transaction isolation "
               + isolationToString(isolation) + "=" + isolation;
         // conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
      } catch (SQLException e) {
         log.warning(e.toString());
         return "";
      }
   }
  
   private final void setInPool(Connection conn, boolean inPool) {
      //if (conn instanceof DebugConnection)
      //   ((DebugConnection)conn).setInPool(inPool); // assert code
   }
   private final boolean isInPool(Connection conn) {
      //if (conn instanceof DebugConnection)
      //   return ((DebugConnection)conn).isInPool();
      return false;
   }
  
   /**
    * Independend of pool.
    * @param doLog
    * @return
    * @throws SQLException
    */
   public Connection createNewConnection(boolean doLog) throws SQLException {
      Connection conn = DriverManager.getConnection(url, user, password);
      if (doLog) {
         log.info(getIsolationLevel(conn));
      }
      if (this.forceIsolationLevel != -1)
         conn.setTransactionIsolation(this.forceIsolationLevel);
      return conn;
   }
  

   private synchronized void addConnectionToPool(boolean doLog) throws SQLException {
      try {
         if (this.connections.size() == this.connections.capacity()) {
            log.severe("Can't add more JDBC connections to pool, capacity="
                  + this.connections.capacity() + " is reached");
            return;
         }
         // Connection conn = DriverManager.getConnection(url, user, password);
         Connection conn = null;
         //if (this.debug)
         //   conn = new DebugConnection(DriverManager.getConnection(url, user, password));
         //else
            conn = DriverManager.getConnection(url, user, password);
         if (doLog) {
            log.info(getIsolationLevel(conn));
         }
         if (this.forceIsolationLevel != -1)
            conn.setTransactionIsolation(this.forceIsolationLevel);
         this.connections.put(conn);
         setInPool(conn, true);
         // log.info(ME, "DriverManager:" + buf.toString());
      }
      catch (InterruptedException e) {
         log.severe("connect: an interrupted exception occured " + e.getMessage());
      }
   }
  
   /**
    * Connects to the DB (so many connections as configured)
    * @param disconnectFirst if 'true' then all connections to the db are closed before reconnecting.
    */
   private void connect(boolean disconnectFirst, boolean doLog) throws SQLException {
      int oldStatus;
      I_StorageProblemListener lst = null;
      synchronized(this) {
         if (disconnectFirst) disconnect();
         for (int i = 0; i < this.capacity; i++) {
            if (log.isLoggable(Level.FINE)) log.fine("initializing DB connection "+ i + " url=" + url + " user=" + user); // + " password=" + password);
            //Logging since JDK 1.3:
            //java.io.OutputStream buf = new java.io.ByteArrayOutputStream();
            //java.io.PrintStream pr = new java.io.PrintStream(buf);
            //DriverManager.setLogStream(pr);
            addConnectionToPool((i==0)&&doLog);
            if (log.isLoggable(Level.FINE))
               log.fine("initialized DB connection "+ i + " success");
         }
         oldStatus = this.status;
         this.status = I_StorageProblemListener.AVAILABLE;
         lst = this.storageProblemListener;
      }
      this.isShutdown = false;
      if (lst != null) lst.storageAvailable(oldStatus);
      log.info("Successfully reconnected to database");
   }

   public String getProp(String key, String def) {
      final String prefix = "queue.persistent.";
      org.xmlBlaster.util.property.Property prop = glob.getProperty();
      def = prop.get(prefix + key, def);
      return pluginProp.getProperty(key, def);
   }
  
   public int getProp(String key, int def) {
      final String prefix = "queue.persistent.";
      org.xmlBlaster.util.property.Property prop = glob.getProperty();
      def = prop.get(prefix + key, def);
      String tmp = pluginProp.getProperty(key, "" + def);
      try {
         return Integer.parseInt(tmp.trim());
      }
      catch (Exception ex) {
         return def;
      }
   }

   /**
    * Is called after the instance is created. It reads the needed properties,
    * sets the driver and then connects to the database as many times as
    * specified in the properties. <p/>
    * Note that prefix is the string which identifies a certain database.
    * It is called prefix because it is the prefix in the xmlBlaster.properties
    * for the properties bound to the database.<br> A pool instance is created
    * for every such database. For example if you want the history queue (the
    * queue which stores the published messages) on a different database than
    * the callback queues, then you could define different databases.
    *
    * <li>prefix.driverName DEFAULTS TO "org.postgresql.Driver"</li> <li>prefix.connectionPoolSize "DEFAULTS TO 2"</li>
    * <li>prefix.url DEFAULTS TO "jdbc:postgresql://localhost/test"</li> <li>prefix.user DEFAULTS TO "postgres"</li>
    * <li>prefix.password  DEFAULTS TO ""</li> </ul>
    */
   public synchronized void initialize(Global glob, Properties pluginProperties)
      throws ClassNotFoundException, SQLException, XmlBlasterException {

      if (this.initialized) return;
      this.glob = glob;
      this.pluginProp = pluginProperties;

      if (log.isLoggable(Level.FINE)) log.fine("initialize");

      // could these also be part of the properties specific to the invoking plugin ?
      org.xmlBlaster.util.property.Property prop = glob.getProperty();
      String xmlBlasterJdbc = prop.get("JdbcDriver.drivers", "org.postgresql.Driver:sun.jdbc.odbc.JdbcOdbcDriver:ORG.as220.tinySQL.dbfFileDriver:oracle.jdbc.driver.OracleDriver");

      // in xmlBlaster.properties file we separate the drivers with a ',' but
      // in the system properties they should be separated with ':' so it may
      // be time to change that in our properties
      //(THIS IS NOT NEEDED ANYMORE (fixed 2002-10-07 Michele)
      // xmlBlasterJdbc = xmlBlasterJdbc.replace(',', ':');

      System.setProperty("jdbc.drivers", xmlBlasterJdbc);

      // Default is:
      //   queue.persistent.url=jdbc:postgresql://localhost/test
      // This has precedence:
      //   cb.queue.persistent.url=jdbc:postgresql://localhost/test
      //   client.queue.persistent.url=jdbc:postgresql://localhost/test

      // TODO:
      //
      // Instance settings:
      // "queue/history/maxEntries" -> "queue=history, maxEntries=5"
      //
      // Class settings:
      // "plugin/QueuePlugin[JDBC][1.0]/className/org.xmlBlaster.util.queue.jdbc.JdbcQueueCommonTablePlugin"
      // "plugin/QueuePlugin[JDBC][1.0]/tableNamePrefix/XB_"

      // the old generic properties (for the defaults) outside the plugin
      this.url = prop.get("queue.persistent.url", "jdbc:postgresql://localhost/test");
      this.user = prop.get("queue.persistent.user", "postgres");
      this.password = prop.get("queue.persistent.password", "");
      this.capacity = prop.get("queue.persistent.connectionPoolSize", MIN_POOL_SIZE);
      if (this.capacity < MIN_POOL_SIZE) {
         log.warning("queue.persistent.connectionPoolSize=" + this.capacity + " is too small, setting it to " + MIN_POOL_SIZE);
         this.capacity = MIN_POOL_SIZE;
      }
      this.queryTimeout = prop.get("queue.persistent.queryTimeout", 0);
      this.connectionBusyTimeout = prop.get("queue.persistent.connectionBusyTimeout", 60000L);
      this.maxWaitingThreads = prop.get("queue.persistent.maxWaitingThreads", 200);
      // these should be handled by the JdbcManager
      this.tableAllocationIncrement = prop.get("queue.persistent.tableAllocationIncrement", 10);
      this.tableNamePrefix = prop.get("queue.persistent.tableNamePrefix", "XB").toUpperCase(); // XB stands for XMLBLASTER
      this.colNamePrefix = prop.get("queue.persistent.colNamePrefix", "").toUpperCase();

      // the property settings specific to this plugin type / version
      this.url = pluginProp.getProperty("url", this.url);
      String dbInstanceName = glob.getStrippedId();
      this.url = ReplaceVariable.replaceFirst(this.url, "$_{xmlBlaster_uniqueId}", dbInstanceName);
      ME = "JdbcConnectionPool-" + this.url;
      this.user = pluginProp.getProperty("user", this.user);
      this.password = pluginProp.getProperty("password", this.password);

      String txt = pluginProp.getProperty("debug", "" + false);
      try {
         this.debug = (new Boolean(txt)).booleanValue();
      }
      catch (Exception ex) {
         log.warning("The property 'debug' was set to '" + txt + "' which can not be parsed as a boolean value, will set it to false");
      }
     
      String help = pluginProp.getProperty("connectionPoolSize", "" + this.capacity);
      try {
         this.capacity = Integer.parseInt(help);
      }
      catch (Exception ex) {
         log.warning("the 'connectionPoolSize' plugin-property is not parseable: '" + help + "' will be using the default '" + this.capacity + "'");
      }
      if (this.capacity < MIN_POOL_SIZE) {
         log.warning("connectionPoolSize=" + this.capacity + " is too small, setting it to " + MIN_POOL_SIZE);
         this.capacity = MIN_POOL_SIZE;
      }

      help = pluginProp.getProperty("connectionBusyTimeout", "" + this.connectionBusyTimeout);
      try {
         this.connectionBusyTimeout = Long.parseLong(help);
      }
      catch (Exception ex) {
         log.warning("the 'connectionBusyTimeout' plugin-property is not parseable: '" + help + "' will be using the default '" + this.connectionBusyTimeout + "'");
      }

      help = pluginProp.getProperty("maxWaitingThreads", "" + this.maxWaitingThreads);
      try {
         this.maxWaitingThreads = Integer.parseInt(help);
      }
      catch (Exception ex) {
         log.warning("the 'maxWaitingThreads' plugin-property is not parseable: '" + help + "' will be using the default '" + this.maxWaitingThreads + "'");
      }

      help = pluginProp.getProperty("queryTimeout", "" + this.queryTimeout);
      try {
         this.queryTimeout = Integer.parseInt(help);
      }
      catch (Exception ex) {
         log.warning("the 'queryTimeout' plugin-property is not parseable: '" + help + "' will be using the default '" + this.queryTimeout + "'");
      }

      help = pluginProp.getProperty("enableBatchMode", "true");
      try {
         this.enableBatchMode = Boolean.getBoolean(help);
      }
      catch (Exception ex) {
         log.warning("the 'enableBatchMode' plugin-property is not parseable: '" + help + "' will be using the default '" + this.enableBatchMode + "'");
      }

      // these should be handled by the JdbcManager
      help = pluginProp.getProperty("tableAllocationIncrement", "" + this.tableAllocationIncrement);
      try {
         this.tableAllocationIncrement = Integer.parseInt(help);
      }
      catch (Exception ex) {
         log.warning("the 'tableAllocationIncrement' plugin-property is not parseable: '" + help + "' will be using the default '" + this.tableAllocationIncrement + "'");
      }

      this.tableNamePrefix = pluginProp.getProperty("tableNamePrefix", this.tableNamePrefix).trim().toUpperCase();
      this.colNamePrefix = pluginProp.getProperty("colNamePrefix", this.colNamePrefix).trim().toUpperCase();

      try {
         this.forceIsolationLevel = Integer.valueOf(pluginProp.getProperty("forceIsoaltionLevel", "-1")).intValue();
      }
      catch(NumberFormatException e) {
         log.warning("Please check your forceIsoaltionLevel:" + e.toString());
      }

      String tmp = pluginProp.getProperty("dbAdmin", "true").trim();
      this.dbAdmin = true;
      if ("false".equalsIgnoreCase(tmp)) this.dbAdmin = false;

      this.configurationIdentifier =  pluginProp.getProperty("configurationIdentifier", null);
      if (this.configurationIdentifier != null)
         this.configurationIdentifier = this.configurationIdentifier.trim();

      help = pluginProp.getProperty("cascadeDeleteSupported", "true");
      try {
         this.cascadeDeleteSupported = Boolean.getBoolean(help);
      }
      catch (Exception ex) {
         log.warning("the 'cascadeDeleteSupported' plugin-property is not parseable: '" + help + "' will be using the default '" + this.cascadeDeleteSupported + "'");
      }

      help = pluginProp.getProperty("nestedBracketsSupported", "true");
      try {
         this.nestedBracketsSupported = Boolean.getBoolean(help);
      }
      catch (Exception ex) {
         log.warning("the 'nestedBracketsSupported' plugin-property is not parseable: '" + help + "' will be using the default '" + this.nestedBracketsSupported + "'");
      }

      if (log.isLoggable(Level.FINEST)) {
         log.finest("initialize -url                    : " + this.url);
         log.finest("initialize -user                   : " + this.user);
         log.finest("initialize -password               : " + this.password);
         log.finest("initialize -max number of conn     : " + this.capacity);
         log.finest("initialize -queryTimeout           : " + this.queryTimeout);
         log.finest("initialize -conn busy timeout      : " + this.connectionBusyTimeout);
         log.finest("initialize -driver list            : " + xmlBlasterJdbc);
         log.finest("initialize -max. waiting Threads   :" + this.maxWaitingThreads);
         log.finest("initialize -tableNamePrefix        :" + this.tableNamePrefix);
         log.finest("initialize -colNamePrefix          :" + this.colNamePrefix);
         log.finest("initialize -dbAdmin                :" + this.dbAdmin);
         log.finest("initialize -cascadeDeleteSupported :" + this.cascadeDeleteSupported);
         log.finest("initialize -nestedBracketsSupported:" + this.nestedBracketsSupported);
         log.finest("initialize -debug                  :" + this.debug);
        
         if (this.configurationIdentifier != null)
            log.finest("initialize -configurationIdentifier:" + this.configurationIdentifier);
      }

      // could block quite a long time if the number of connections is big
      // or if the connection to the DB is slow.
      this.connections = new BoundedLinkedQueue();
      this.connections.setCapacity(this.capacity);
      try {
         // initializing and establishing of connections to DB (but first disconnect if already connected)
         final boolean disconnectFirst = true;
         connect(disconnectFirst, true);

         parseMapping(prop);
         if (log.isLoggable(Level.FINEST)) dumpMetaData();
         this.initialized = true;
      }
      catch (SQLException ex) {
         if (firstConnectError) {
            firstConnectError = false;
            log.severe(" connecting to DB, error code : '" + ex.getErrorCode() + " : " + ex.getMessage() + "' DB configuration details follow (check if the DB is running)");
            log.info("diagnostics: initialize -url                 : '" + url + "'");
            log.info("diagnostics: initialize -user                : '" + user + "'");
            log.info("diagnostics: initialize -password            : '" + password + "'");
            log.info("diagnostics: initialize -max number of conn  : '" + this.capacity + "'");
            log.info("diagnostics: initialize -queryTimeout        : '" + this.queryTimeout + "'");
            log.info("diagnostics: initialize -conn busy timeout   : '" + this.connectionBusyTimeout + "'");
            log.info("diagnostics: initialize -driver list         : '" + xmlBlasterJdbc + "'");
            log.info("diagnostics: initialize -max. waiting Threads: '" + this.maxWaitingThreads + "'");
            log.finest("diagnostics: initialize -tableNamePrefix     :" + this.tableNamePrefix);
            log.finest("diagnostics: initialize -colNamePrefix       :" + this.colNamePrefix);
            ex.printStackTrace();
         }
         else {
            if (log.isLoggable(Level.FINE)) log.fine(" connecting to DB, error code: '" + ex.getErrorCode() + " : " + ex.getMessage() + "' DB configuration details follow (check if the DB is running)");
         }

         // clean up the connections which might have been established
         // even if it probably won't help that much ...
         disconnect(-1L, false);
         throw ex;
      }
      log.info("Connections to DB '" + url + "' successfully established.");
   }


   /**
    * @return the prefix for the name of the tables to associate to the queues
    */
   public String getTableNamePrefix() {
      return this.tableNamePrefix;
   }
  
  
   public String getUserName() {
      return this.user;
   }

   /**
    * @return the prefix for the name of the columns in each DB table
    */
   public String getColNamePrefix() {
      return this.colNamePrefix;
   }


   /**
    * @return the number of tables to add each time no free tables are available
    *         when creating a new queue.
    */
   public int getTableAllocationIncrement() {
      return this.tableAllocationIncrement;
   }


   /** This method is used in the init method */
   private Hashtable parseMapping(org.xmlBlaster.util.property.Property prop)
         throws XmlBlasterException, SQLException {
      if (log.isLoggable(Level.FINER)) log.finer("parseMapping");
      if (this.isShutdown) connect(false, false);


      String mappingKey = null;
     
      if (this.configurationIdentifier == null) {
         Connection conn = null;
         try {
            conn = this.getConnection();
            mappingKey = conn.getMetaData().getDatabaseProductName();
            // replace "Microsoft SQL Server" to "MicrosoftSQLServer"
            // blanks are not allowed, thanks to zhang zhi wei
            mappingKey = ReplaceVariable.replaceAll(mappingKey, " ", "");
            if (mappingKey.indexOf("Firebird")!=-1) {
              //Firebird2.1/WI-V2.1.0.17798Firebird2.1/tcp(computername)/P10
              mappingKey = "Firebird";
            }
            if (log.isLoggable(Level.FINE))
               log.fine("parseMapping: the mapping will be done for the keyword (which is here is the DB product name)'" + mappingKey + "'");
         }
         finally {
            if (conn != null) releaseConnection(conn);
         }
      }
      else {
         mappingKey = this.configurationIdentifier;
         if (log.isLoggable(Level.FINE))
            log.fine("parseMapping: the mapping will be done for the keyword (name given in the plugin)'" + mappingKey + "'");
      }

//      String mappingText = prop.get("JdbcDriver." + productName + ".mapping", "");
      String mappingText = prop.get("JdbcDriver.mapping[" + mappingKey + "]", "");
      if (log.isLoggable(Level.FINE))
         log.fine("parseMapping: the string to be mapped is '" + mappingText + "'");
     
      this.mapping = new Hashtable();
      // StringTokenizer tokenizer = new StringTokenizer(mappingText, ",");
      StrTokenizer tokenizer = new StrTokenizer(mappingText, ',', '"');
      XmlBlasterException ex = null;
      while (tokenizer.hasNext()) {
         String singleMapping = tokenizer.nextToken();
         int pos = singleMapping.indexOf("=");
         if (pos < 0)
            ex = new XmlBlasterException(this.glob, ErrorCode.RESOURCE_CONFIGURATION_PLUGINFAILED, ME, "syntax in xmlBlaster.properties for " + singleMapping +
                " is wrong: no equality sign between key and value");
//            ex = new XmlBlasterException(ME, "syntax in xmlBlaster.properties for " + singleMapping +
//                " is wrong: no equality sign between key and value");
         String key = singleMapping.substring(0, pos);
         String value = singleMapping.substring(pos + 1, singleMapping.length());
         if (log.isLoggable(Level.FINE)) log.fine("parseMapping: mapping " + key + " to " + value);
         this.mapping.put(key, value);
      }
      if (ex != null) {
         if (log.isLoggable(Level.FINE))
            log.fine("parseMapping: Exception occured: " + ex.getMessage());
         throw ex;
      }
      return this.mapping;
   }

   /**
    * The mapping is  taken out of the xmlBlaster.properties file. It is an
    * Hashtable where the keys are the logical names used for a type in the
    * JdbcQueuePlugin and the values are the real names to be used in the database used (which may be vendor specific).
    */
   public Hashtable getMapping() {
      return this.mapping;
   }

   synchronized private final void disconnect(long waitTime, boolean silent) {
      if (waitTime < 5L) waitTime = 5L;
      Connection conn = null;
      for (int i=0; i < this.connections.size(); i++) {
         try {
            conn = get(waitTime);           
            if (conn == null) break;
            // It is <b>strongly recommended</b> that an application explicitly
            // commits or rolls back an active transaction prior to calling the
            // <code>close</code> method.  If the <code>close</code> method is called
            // and there is an active transaction, the results are implementation-defined.
            if (getUrl().startsWith("jdbc:hsqldb:")) {
               try { // HSQLDB only: http://www.hsqldb.org/web/hsqlDocsFrame.html
                  Statement stmt = conn.createStatement();
                  stmt.execute("SHUTDOWN"); // or shutdown=true to cleanup .lck file!
                  stmt.close();
               }
               catch (Throwable e) {
                  log.warning(e.toString());
               }
            }
            conn.close();
            if (log.isLoggable(Level.FINE)) log.fine("connection " + conn + " disconnected ( object address: " + conn + ")");
         }
         catch (Throwable ex) {
            if (silent) {
               if (log.isLoggable(Level.FINE)) log.fine("could not close connection " + conn + " correctly but resource is set to null. reason " + ex.toString());
            }
            else {
               log.severe("could not close connection " + conn + " correctly but resource is set to null. reason " + ex.toString());
            }
            ex.printStackTrace();
         }
      }
   }

   /**
    * Closes all connections to the Database. It should not throw any
    * exception. Exceptions coming from the backend or the communication
    * layer are catched and logged. When this occurs, the affected connections are set to null.
    */
   synchronized public void disconnect() {
      if (log.isLoggable(Level.FINER)) log.finer("disconnect invoked");
      disconnect(-1L, false);
   }

   public void finalize() {
      shutdown();
   }


   /**
    * returns true if the connection is temporarly lost (and the pool is polling
    * for new connections)
    */
   public final int getStatus() {
      return this.status;
   }

   /**
    * informs this pool that the connection to the DB has been lost
    */
   public final void setDatabaseLost() {
      if (this.status != I_StorageProblemListener.UNAVAILABLE) {
         int oldStatus = this.status;
         synchronized (this) {
            if (this.status == I_StorageProblemListener.UNAVAILABLE) return;
            oldStatus = this.status;
            if (this.status != I_StorageProblemListener.UNAVAILABLE) {
               this.status = I_StorageProblemListener.UNAVAILABLE;
               this.waitingForReentrantConnections = true;
            }
         }

         // start polling to wait until all connections have returned
         // start pooling to see if new connections can be established again
         this.glob.getJdbcConnectionPoolTimer().addTimeoutListener(this, this.reconnectionTimeout, null);

         I_StorageProblemListener lst = this.storageProblemListener;
         if (lst != null)
            lst.storageUnavailable(oldStatus);
      }
   }


   /**
    * Returns a free connection. If no connection is currently available
    * it waits no more than what specified in the configuration. If after that time
    * still no connection is available it throws an XmlBlasterException. It also throws
    * an XmlBlasterException if the number of threads which are waiting is already
    * too high (also configurable).
    */
   public Connection getConnection() throws XmlBlasterException {
      if (this.status != I_StorageProblemListener.AVAILABLE)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME, "getConnection: Connection Lost. Going in polling modus");
      if (this.waitingCalls >= this.maxWaitingThreads)
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_TOO_MANY_THREADS, ME, "Too many threads waiting for a connection to the DB. Increase the property 'queue.persistent.maxWaitingThreads'");

      synchronized (this) {
         this.waitingCalls++;
      }
      if (log.isLoggable(Level.FINER)) log.finer("getConnection " + this.connections.size() + " waiting calls: " + this.waitingCalls);
      try {
         if (this.isShutdown) connect(false, false);
         return get(this.connectionBusyTimeout);
      }
      catch (SQLException ex) {
         String additionalMsg = "check system classpath and 'jdbc.drivers' system property\n";
         additionalMsg += "'classpath' is: '" + System.getProperty("classpath", "") + "'\n";
         additionalMsg +=  "'jdbc.drivers' is: '" + System.getProperty("jdbc.drivers", "") + "'\n";
         throw new XmlBlasterException(this.glob, ErrorCode.RESOURCE_DB_UNAVAILABLE, ME + ".getConnection()", ex.getMessage() + " " + additionalMsg);
      }
     
      finally {
         synchronized (this) {
            this.waitingCalls--;
         }
      }
   }

   /**
    * Used to give back a connection to the pool. If the pool is already full
    * it will throw an exception. If the success flag is true, it well release the
    * connection back to the pool, otherwise it will throw away the current connection
    * and add to the pool a new one.
    */
   public void releaseConnection(Connection conn, boolean success) throws XmlBlasterException {
       if (success)
          releaseConnection(conn);
       else
          discardConnection(conn);
   }

   /**
    * Used to give back a connection to the pool. If the pool is already full
    * it will throw an exception.
    */
   private void releaseConnection(Connection conn) throws XmlBlasterException {
      if (log.isLoggable(Level.FINER)) log.finer("releaseConnection " + this.connections.size() + " waiting calls: " + this.waitingCalls);
      try {
         SQLWarning warns = conn.getWarnings();
         /*
            while (warns != null) {
               log.warn(ME, "errorCode=" + warns.getErrorCode() + " state=" + warns.getSQLState() + ": " + warns.toString().trim());
               Thread.currentThread().dumpStack();
               warns = warns.getNextWarning();
            }
         */
         if (log.isLoggable(Level.FINE)) {
            while (warns != null) {
               log.fine("errorCode=" + warns.getErrorCode() + " state=" + warns.getSQLState() + ": " + warns.toString().trim());
               warns = warns.getNextWarning();
            }
         }
         conn.clearWarnings(); // free memory
      }
      catch (Throwable e) {
         log.warning("clearWarnings() failed: " + e.toString());
      }
      boolean isOk = put(conn); // if an exception occured it would be better to throw away the connection and make a new one
      if (!isOk) {
         log.severe("the connection pool is already full: " + ThreadLister.listAllThreads());
      }

   }


   public void dumpMetaData() {
      Connection conn = null;
      try {
         if (this.isShutdown) connect(false, false);
         conn = getConnection();
         DatabaseMetaData metaData = conn.getMetaData();

         log.info("--------------- DUMP OF METADATA FOR THE DB START---------------------");
         String driverName = metaData.getDriverName();
         String productName = metaData.getDatabaseProductName();
         log.info("Driver name          :'" + driverName +"', product name: '" + productName + "'");
         log.info("max binary length    : " + metaData.getMaxBinaryLiteralLength());
         log.info("max char lit. length : " + metaData.getMaxCharLiteralLength());
         log.info("max column length    : " + metaData.getMaxColumnNameLength());
         log.info("max cols. in table   : " + metaData.getMaxColumnsInTable());
         log.info("max connections      : " + metaData.getMaxConnections());
         log.info("max statement length : " + metaData.getMaxStatementLength());
         log.info("max nr. of statements: " + metaData.getMaxStatements());
         log.info("max tablename length : " + metaData.getMaxTableNameLength());
         log.info("url                  : " + metaData.getURL());
         log.info("support for trans.   : " + metaData.supportsTransactions());
         log.info("support transactions : " + getIsolationLevel(conn));
         log.info("--------------- DUMP OF METADATA FOR THE DB END  ---------------------");

      }
      catch (XmlBlasterException ex) {
         log.severe("dumpMetaData: exception: " + ex.getMessage());
         ex.printStackTrace();
      }
      catch (SQLException ex) {
         log.severe("dumpMetaData: SQL exception: " + ex.getMessage());
         ex.printStackTrace();
      }
      finally {
         try {
            if (conn != null) releaseConnection(conn);
         }
         catch (XmlBlasterException ex) {
            log.severe("dumpMetaData:  releasing the connection: " + ex.getMessage());
            ex.printStackTrace();
         }
      }
   }


   public boolean isDbAdmin() {
      return this.dbAdmin;
   }


   public int getQueryTimeout() {
      return this.queryTimeout;
   }


   public static void main(String[] args) {

      //here starts the main method ...
      Connection conn = null;
      try {
         Global glob = Global.instance();
         glob.init(args);

         log.info("starting the application: The Tests start here ");

         String prefix = "cb.queue.persistent";
         org.xmlBlaster.util.property.Property prop = glob.getProperty();
         String xmlBlasterJdbc = prop.get("JdbcDriver.drivers", "org.postgresql.Driver");
         System.setProperty("jdbc.drivers", xmlBlasterJdbc);
         String url = prop.get(prefix + ".url", "");
         String user = prop.get(prefix + ".user", "postgres");
         String password = prop.get(prefix + ".password", "");

         if (log.isLoggable(Level.FINEST)) {
            log.finest("initialize -prefix is           : " + prefix);
            log.finest("initialize -url                 : " + url);
            log.finest("initialize -user                : " + user);
            log.finest("initialize -password            : " + password);
            log.finest("initialize -driver list         : " + xmlBlasterJdbc);
         }

         log.info("establishing the connection");
         conn = DriverManager.getConnection(url, user, password);
         log.info("connection established. Sleeping 1 second");
         Thread.sleep(1000L);
         log.info("finished");
      }
      catch (/*SQL*/Exception ex) {
         if (log !=null)
            log.severe(" connecting to DB, error code: " + " : " + ex.getMessage());
            ex.printStackTrace();
      }
      finally {
         try {
            if (log != null) log.info("closing the connection");
            if (conn != null) {
               conn.close();
            }
            if (log != null) log.info("connection closed");
         }
         catch (Exception ex) {
            ex.printStackTrace();
         }
      }
   }

   public synchronized void shutdown() {
      if (log.isLoggable(Level.FINER)) log.finer("shutdown");
      disconnect();
//      this.initialized = false;
      this.isShutdown = true;
   }

   synchronized public void registerManager(Object manager) {
      if (log.isLoggable(Level.FINER)) log.finer("registerManager, number of managers registered (before registering this one): '" + this.managerCount + "'");
      if (manager == null) return;
      this.managerCount++;
   }

   synchronized public void unregisterManager(Object manager) {
      if (log.isLoggable(Level.FINER)) log.finer("unregisterManager, number of managers registered (still including this one): '" + this.managerCount + "'");
      if (manager == null) return;
      this.managerCount--;
      if (this.managerCount < 1) shutdown();
   }

   /**
    * The batch mode means that insertions in the database are made in batch mode,
    * i.e. several entries in one sweep. This can increase perfomance significantly
    * on some DBs.  
    *   
    * @return true if batch mode has been enabled, false otherwise (defaults to true).
    */
   public boolean isBatchModeEnabled() {
      return this.enableBatchMode;
   }  

   /**
    *
    * @return true if 'cascadeDeleteSupported' has been set to true
    */
   public boolean isCascadeDeleteSuppported() {
      return this.cascadeDeleteSupported;
   }
  
   /**
    *
    * @return true if 'nestedBracketsSupported' has been set to true
    */
   public boolean isNestedBracketsSuppported() {
      return this.nestedBracketsSupported;
   }

   /**
    * @return the url
    */
   public String getUrl() {
      return (this.url==null) ? "" : this.url;
   }

}

TOP

Related Classes of org.xmlBlaster.util.queue.jdbc.JdbcConnectionPool

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.