You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@avalon.apache.org by le...@apache.org on 2002/04/22 05:04:27 UTC

cvs commit: jakarta-avalon-excalibur/datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test TableIdGeneratorJdbcTestCase.java TableIdGeneratorJdbcTestCase.xtest TableIdGeneratorMultithreadedJdbcTestCase.java TableIdGeneratorMultithreadedJdbcTestCase.xtest

leif        02/04/21 20:04:27

  Modified:    datasource build.xml default.properties
  Added:       datasource/src/java/org/apache/avalon/excalibur/datasource/cluster
                        AbstractDataSourceCluster.java
                        DefaultHashedDataSourceCluster.java
                        DefaultIndexedDataSourceCluster.java
                        DefaultRoundRobinDataSourceCluster.java
                        HashedDataSourceCluster.java
                        IndexedDataSourceCluster.java
                        RoundRobinDataSourceCluster.java
               datasource/src/java/org/apache/avalon/excalibur/datasource/ids
                        AbstractDataSourceBlockIdGenerator.java
                        AbstractDataSourceIdGenerator.java
                        AbstractIdGenerator.java IdException.java
                        IdGenerator.java SequenceIdGenerator.java
                        TableIdGenerator.java
               datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test
                        TableIdGeneratorJdbcTestCase.java
                        TableIdGeneratorJdbcTestCase.xtest
                        TableIdGeneratorMultithreadedJdbcTestCase.java
                        TableIdGeneratorMultithreadedJdbcTestCase.xtest
  Log:
  Moved datasource.ids and datasource.cluster packages into the datasource
  subproject.
  
  Revision  Changes    Path
  1.16      +26 -0     jakarta-avalon-excalibur/datasource/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-avalon-excalibur/datasource/build.xml,v
  retrieving revision 1.15
  retrieving revision 1.16
  diff -u -r1.15 -r1.16
  --- build.xml	22 Apr 2002 00:44:28 -0000	1.15
  +++ build.xml	22 Apr 2002 03:04:26 -0000	1.16
  @@ -36,6 +36,7 @@
           <pathelement location="${excalibur-logger.jar}"/>
           <pathelement location="${excalibur-concurrent.jar}"/>
           <pathelement location="${excalibur-collections.jar}"/>
  +        <pathelement location="${test.jdbc.driver.jar}"/>
           <path refid="project.class.path"/>
       </path>
       <property name="cp" refid="test.class.path"/>
  @@ -213,11 +214,36 @@
   
           <mkdir dir="${build.lib}"/>
   
  +        <!-- excalibur-datasource jar -->
           <jar jarfile="${build.lib}/${jar.name}"
               basedir="${build.classes}"
               compress="${build.compress}"
               manifest="${build.conf}/MANIFEST.MF">
               <exclude name="**/test/**"/>
  +            <exclude name="**/cluster/**"/>
  +            <exclude name="**/ids/**"/>
  +            <zipfileset dir="${build.conf}" prefix="META-INF/">
  +                <include name="LICENSE.txt"/>
  +            </zipfileset>
  +        </jar>
  +
  +        <!-- excalibur-datasource-clister jar -->
  +        <jar jarfile="${build.lib}/${jar.cluster.name}"
  +            basedir="${build.classes}"
  +            compress="${build.compress}"
  +            manifest="${build.conf}/MANIFEST.MF">
  +            <include name="**/cluster/**"/>
  +            <zipfileset dir="${build.conf}" prefix="META-INF/">
  +                <include name="LICENSE.txt"/>
  +            </zipfileset>
  +        </jar>
  +
  +        <!-- excalibur-datasource-ids jar -->
  +        <jar jarfile="${build.lib}/${jar.ids.name}"
  +            basedir="${build.classes}"
  +            compress="${build.compress}"
  +            manifest="${build.conf}/MANIFEST.MF">
  +            <include name="**/ids/**"/>
               <zipfileset dir="${build.conf}" prefix="META-INF/">
                   <include name="LICENSE.txt"/>
               </zipfileset>
  
  
  
  1.7       +2 -0      jakarta-avalon-excalibur/datasource/default.properties
  
  Index: default.properties
  ===================================================================
  RCS file: /home/cvs/jakarta-avalon-excalibur/datasource/default.properties,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- default.properties	16 Apr 2002 12:00:40 -0000	1.6
  +++ default.properties	22 Apr 2002 03:04:26 -0000	1.7
  @@ -109,6 +109,8 @@
   
   #  name of jar file
   jar.name = ${name}-${version}.jar
  +jar.cluster.name = ${name}-cluster-${version}a.jar
  +jar.ids.name = ${name}-ids-${version}a.jar
   
   #  property indicating directory where all distribution archives are placed
   dist.base = distributions
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/AbstractDataSourceCluster.java
  
  Index: AbstractDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  import org.apache.avalon.excalibur.datasource.NoValidConnectionException;
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.component.ComponentSelector;
  import org.apache.avalon.framework.component.Composable;
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.logger.AbstractLogEnabled;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public abstract class AbstractDataSourceCluster
      extends AbstractLogEnabled
      implements Composable, Configurable, Initializable, Disposable, ThreadSafe
  {
  
      /** ComponentManager which created this component */
      protected ComponentManager m_manager;
  
      protected int m_size;
      private String[] m_dataSourceNames;
      private ComponentSelector m_dbSelector;
      private DataSourceComponent[] m_dataSources;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public AbstractDataSourceCluster()
      {
      }
  
      /*---------------------------------------------------------------
       * AbstractDataSourceCluster Methods
       *-------------------------------------------------------------*/
      /**
       * Returns the number of DataSources in the cluster.
       *
       * @return size of the cluster.
       */
      public int getClusterSize()
      {
          return m_size;
      }
  
      /**
       * Gets a Connection to a database given an index.
       *
       * @param index Index of the DataSource for which a connection is to be returned.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      public Connection getConnectionForIndex( int index ) throws SQLException
      {
          if( ( index < 0 ) || ( index >= m_size ) )
          {
              throw new NoValidConnectionException(
                  "index (" + index + ") must be in the range 0 to " + ( m_size - 1 ) );
          }
          return m_dataSources[ index ].getConnection();
      }
  
      /*---------------------------------------------------------------
       * Composable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to tell the component which ComponentManager
       *  is controlling it.
       *
       * @param ComponentManager which curently owns the component.
       */
      public void compose( ComponentManager manager )
      {
          m_manager = manager;
      }
  
      /*---------------------------------------------------------------
       * Configurable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to configure the component.
       *
       * @param configuration configuration info used to setup the component.
       *
       * @throws ConfigurationException if there are any problems with the configuration.
       */
      public void configure( Configuration configuration ) throws ConfigurationException
      {
          // Get the size
          m_size = configuration.getAttributeAsInteger( "size" );
          if( m_size < 1 )
          {
              throw new ConfigurationException(
                  "Invalid value (" + m_size + ") for size attribute." );
          }
  
          // Read in the data source names.
          m_dataSourceNames = new String[ m_size ];
          Configuration[] dataSourceConfigs = configuration.getChildren( "dbpool" );
          for( int i = 0; i < dataSourceConfigs.length; i++ )
          {
              int index = dataSourceConfigs[ i ].getAttributeAsInteger( "index" );
              if( ( index < 0 ) || ( index >= m_size ) )
              {
                  throw new ConfigurationException( "The dbpool with index=\"" + index +
                                                    "\" is invalid.  Index must be in the range 0 to " + ( m_size - 1 ) );
              }
              if( m_dataSourceNames[ index ] != null )
              {
                  throw new ConfigurationException( "Only one dbpool with index=\"" + index +
                                                    "\" can be defined." );
              }
              m_dataSourceNames[ index ] = dataSourceConfigs[ i ].getValue();
          }
  
          // Make sure that all of the dbpools were defined
          for( int i = 0; i < m_dataSourceNames.length; i++ )
          {
              if( m_dataSourceNames[ i ] == null )
              {
                  throw new ConfigurationException( "Expected a dbpool with index=\"" + i + "\"" );
              }
          }
      }
  
      /*---------------------------------------------------------------
       * Initializable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to initialize the component.
       *
       * @throws Exception if there were any problems durring initialization.
       */
      public void initialize() throws Exception
      {
          // Get references to a data sources
          m_dbSelector =
              (ComponentSelector)m_manager.lookup( DataSourceComponent.ROLE + "ClusterSelector" );
          m_dataSources = new DataSourceComponent[ m_size ];
          for( int i = 0; i < m_dataSourceNames.length; i++ )
          {
              m_dataSources[ i ] = (DataSourceComponent)m_dbSelector.select( m_dataSourceNames[ i ] );
          }
      }
  
      /*---------------------------------------------------------------
       * Disposable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to dispose the component.
       */
      public void dispose()
      {
          // Free up the data source
          if( m_dbSelector != null )
          {
              if( m_dataSources != null )
              {
                  for( int i = 0; i < m_dataSources.length; i++ )
                  {
                      if( m_dataSources[ i ] != null )
                      {
                          m_dbSelector.release( m_dataSources[ i ] );
                      }
                  }
  
                  m_dataSources = null;
              }
  
              m_manager.release( m_dbSelector );
  
              m_dbSelector = null;
          }
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/DefaultHashedDataSourceCluster.java
  
  Index: DefaultHashedDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.NoValidConnectionException;
  
  /**
   * The DefaultHashedDataSourceCluster requires that the user specify an object or a hashCode
   *  which will be used consistantly select a member DataSource form a cluster for each connection
   *  request.  Calls to getConnection() will throw an exception.  Components which make use of
   *  this class must call either the getConnectionForHashObject( Object hashObject) or the
   *  getConnectionForHashCode( int hashCode ) methods instead.
   * <p>
   * This form of Clustering is useful in cases where data can be reliably accessed in a repeatable
   *  manner.  For example a web site's visitor information could be accessed by using a String
   *  containing the visitor's username as a hashObject.  This would allow visitor information to be
   *  spread across several database servers.
   * <p>
   * The Configuration for a 2 database cluster is like this:
   *
   * <pre>
   *   &lt;datasources&gt;
   *     &lt;hashed-cluster name="mydb-cluster" size="2"&gt;
   *       &lt;dbpool index="0"&gt;mydb-0&lt;/dbpool&gt;
   *       &lt;dbpool index="1"&gt;mydb-1&lt;/dbpool&gt;
   *     &lt;/hashed-cluster&gt;
   *   &lt;/datasources&gt;
   *   &lt;cluster-datasources&gt;
   *     &lt;jdbc name="mydb-0"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host0/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *     &lt;jdbc name="mydb-1"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host1/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *   &lt;/cluster-datasources&gt;
   * </pre>
   *
   * With the following roles declaration:
   *
   * <pre>
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
   *     shorthand="datasources"
   *     default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *     &lt;hint shorthand="hashed-cluster"
   *       class="org.apache.avalon.excalibur.datasource.cluster.DefaultHashedDataSourceCluster"/&gt;
   *   &lt;/role&gt;
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentClusterSelector"
   *       shorthand="cluster-datasources"
   *       default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *   &lt;/role&gt;
   * </pre>
   *
   * A hashed-cluster definition enforces that the configuration specify a size.  This size must
   *  equal the number of datasources referenced as being members of the cluster.  Any datasource can
   *  be a member of the cluster.
   * <p>
   * The hashed-cluster can be obtained in the same manner as a non-clustered datasource.  The only
   *  difference is in how it is used.  The HashedDataSourceCluster requires that the caller provide
   *  an object or a hashCode to use when requesting a connection.
   * <p>
   * The following code demonstrates a change that can be made to database enabled components so that
   *  they will be able to work with both HashedDataSourceCluster DataSources and regular
   *  DataSources.
   * <p>
   * old:
   * <pre>
   *   Connection connection = m_dataSource.getConnection();
   * </pre>
   *
   * new:
   * <pre>
   *   Connection connection;
   *   if ( m_dataSource instanceof HashedDataSourceCluster )
   *   {
   *     connection = ((HashedDataSourceCluster)m_dataSource).getConnectionForHashObject( hashObject );
   *   }
   *   else
   *   {
   *     connection = m_dataSource.getConnection();
   *   }
   * </pre>
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class DefaultHashedDataSourceCluster
      extends AbstractDataSourceCluster
      implements HashedDataSourceCluster
  {
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public DefaultHashedDataSourceCluster()
      {
      }
  
      /*---------------------------------------------------------------
       * DataSourceComponent Methods
       *-------------------------------------------------------------*/
      /**
       * Not supported in this component.  Will throw a NoValidConnectionException.
       */
      public Connection getConnection() throws SQLException
      {
          throw new NoValidConnectionException(
              "getConnection() should not be called for a " + getClass().getName() + ".  " +
              "Please verify your configuration." );
      }
  
      /*---------------------------------------------------------------
       * HashedDataSourceCluster Methods
       *-------------------------------------------------------------*/
      // public int getClusterSize()
      //   declared in AbstractDataSourceCluster
  
      /**
       * Gets a Connection to a database given a hash object.
       *
       * @param hashObject Object whose hashCode will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      public Connection getConnectionForHashObject( Object hashObject ) throws SQLException
      {
          return getConnectionForIndex( getIndexForHashObject( hashObject ) );
      }
  
      /**
       * Gets a Connection to a database given a hash code.
       *
       * @param hashCode HashCode which will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      public Connection getConnectionForHashCode( int hashCode ) throws SQLException
      {
          return getConnectionForIndex( getIndexForHashCode( hashCode ) );
      }
  
      // public Connection getConnectionForIndex( int index ) throws SQLException
      //   declared in AbstractDataSourceCluster
  
      /**
       * Gets the index which will be resolved for a given hashCode.  This can be used
       *  by user code to optimize the use of DataSource Clusters.
       * <p>
       * Subclasses can override this method to get different behavior.
       * <p>
       * By default the index = getIndexForHashCode( hashObject.hashCode() )
       *
       * @param hashObject Object whose hashCode will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       */
      public int getIndexForHashObject( Object hashObject )
      {
          return getIndexForHashCode( hashObject.hashCode() );
      }
  
      /**
       * Gets the index which will be resolved for a given hashCode.  This can be used
       *  by user code to optimize the use of DataSource Clusters.
       * <p>
       * Subclasses can override this method to get different behavior.
       * <p>
       * By default the index = hashCode % getClusterSize()
       *
       * @param hashCode HashCode which will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       */
      public int getIndexForHashCode( int hashCode )
      {
          // DEVELOPER WARNING:
          // If you change the way the hashCode is calculated, you WILL BREAK
          //  things for existing users, so please do so only after much thought.
  
          // Hash code may be negative, Make them all positive by using the unsigned int value.
          long lHashCode = ( (long)hashCode ) & 0xffffffffL;
  
          return (int)( lHashCode % getClusterSize() );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/DefaultIndexedDataSourceCluster.java
  
  Index: DefaultIndexedDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.NoValidConnectionException;
  
  /**
   * The DefaultIndexedDataSourceCluster requires that the user implement their own method of
   *  selecting which DataSource in the cluster to use for each connection request.  Calls to
   *  getConnection() will throw an exception.  Components which make use of this class must call
   *  the getConnectionForIndex(int index) method instead.
   * <p>
   * The Configuration for a 2 database cluster is like this:
   *
   * <pre>
   *   &lt;datasources&gt;
   *     &lt;indexed-cluster name="mydb-cluster" size="2"&gt;
   *       &lt;dbpool index="0"&gt;mydb-0&lt;/dbpool&gt;
   *       &lt;dbpool index="1"&gt;mydb-1&lt;/dbpool&gt;
   *     &lt;/indexed-cluster&gt;
   *   &lt;/datasources&gt;
   *   &lt;cluster-datasources&gt;
   *     &lt;jdbc name="mydb-0"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host0/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *     &lt;jdbc name="mydb-1"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host1/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *   &lt;/cluster-datasources&gt;
   * </pre>
   *
   * With the following roles declaration:
   *
   * <pre>
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
   *     shorthand="datasources"
   *     default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *     &lt;hint shorthand="indexed-cluster"
   *       class="org.apache.avalon.excalibur.datasource.cluster.DefaultIndexedDataSourceCluster"/&gt;
   *   &lt;/role&gt;
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentClusterSelector"
   *       shorthand="cluster-datasources"
   *       default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *   &lt;/role&gt;
   * </pre>
   *
   * An indexed-cluster definition enforces that the configuration specify a size.  This size must
   *  equal the number of datasources referenced as being members of the cluster.  Any datasource can
   *  be a member of the cluster.
   * <p>
   * The indexed-cluster can be obtained in the same manner as a non-clustered datasource.  The only
   *  difference is in how it is used.  The IndexedDataSourceCluster requires that the caller specify
   *  the index of the cluster member to use when requesting a connection.
   * <p>
   * The following code demonstrates a change that can be made to database enabled components so that
   *  they will be able to work with both IndexedDataSourceCluster DataSources and regular
   *  DataSources.
   * <p>
   * old:
   * <pre>
   *   Connection connection = m_dataSource.getConnection();
   * </pre>
   *
   * new:
   * <pre>
   *   Connection connection;
   *   if ( m_dataSource instanceof IndexedDataSourceCluster )
   *   {
   *     connection = ((IndexedDataSourceCluster)m_dataSource).getConnectionForIndex( index );
   *   }
   *   else
   *   {
   *     connection = m_dataSource.getConnection();
   *   }
   * </pre>
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class DefaultIndexedDataSourceCluster
      extends AbstractDataSourceCluster
      implements IndexedDataSourceCluster
  {
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public DefaultIndexedDataSourceCluster()
      {
      }
  
      /*---------------------------------------------------------------
       * DataSourceComponent Methods
       *-------------------------------------------------------------*/
      /**
       * Not supported in this component.  Will throw a NoValidConnectionException.
       */
      public Connection getConnection() throws SQLException
      {
          throw new NoValidConnectionException(
              "getConnection() should not be called for a " + getClass().getName() + ".  " +
              "Please verify your configuration." );
      }
  
      /*---------------------------------------------------------------
       * IndexedDataSourceCluster Methods
       *-------------------------------------------------------------*/
      // public int getClusterSize()
      //   declared in AbstractDataSourceCluster
  
      // public Connection getConnectionForIndex( int index ) throws SQLException
      //   declared in AbstractDataSourceCluster
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/DefaultRoundRobinDataSourceCluster.java
  
  Index: DefaultRoundRobinDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.NoValidConnectionException;
  
  /**
   * The DefaultRoundRobinDataSourceCluster allows the user to specify a cluster of DataSources
   *  which all act as one.  The Cluster works by cycling through its member DataSources returning
   *  a connection from a different one with each call to getConnection().
   * <p>
   * This form of Clustering has the benefit that it can be used by components without requiring
   *  any changes.  But care must be taken as to the kind of data written or read from the database.
   *  Wich this clustering method, there is no control over which DataSource will provide a
   *  connection for any given call.
   * <p>
   * Round Robin Clusters are useful in cases where lots of read-only data needs to be accessed and
   *  multiple copies of the data can be stored on different database servers to balance load.
   * <p>
   * The Configuration for a 2 database cluster is like this:
   *
   * <pre>
   *   &lt;datasources&gt;
   *     &lt;roundrobin-cluster name="mydb-cluster" size="2"&gt;
   *       &lt;dbpool index="0"&gt;mydb-0&lt;/dbpool&gt;
   *       &lt;dbpool index="1"&gt;mydb-1&lt;/dbpool&gt;
   *     &lt;/roundrobin-cluster&gt;
   *   &lt;/datasources&gt;
   *   &lt;cluster-datasources&gt;
   *     &lt;jdbc name="mydb-0"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host0/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *     &lt;jdbc name="mydb-1"&gt;
   *       &lt;pool-controller min="1" max="10"/&gt;
   *       &lt;auto-commit&gt;true&lt;/auto-commit&gt;
   *       &lt;driver&gt;com.database.jdbc.JdbcDriver&lt;/driver&gt;
   *       &lt;dburl&gt;jdbc:driver://host1/mydb&lt;/dburl&gt;
   *       &lt;user&gt;username&lt;/user&gt;
   *       &lt;password&gt;password&lt;/password&gt;
   *     &lt;/jdbc&gt;
   *   &lt;/cluster-datasources&gt;
   * </pre>
   *
   * With the following roles declaration:
   *
   * <pre>
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
   *       shorthand="datasources"
   *       default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *     &lt;hint shorthand="roundrobin-cluster"
   *         class="org.apache.avalon.excalibur.datasource.cluster.DefaultRoundRobinDataSourceCluster"/&gt;
   *   &lt;/role&gt;
   *   &lt;role name="org.apache.avalon.excalibur.datasource.DataSourceComponentClusterSelector"
   *       shorthand="cluster-datasources"
   *       default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *     &lt;hint shorthand="jdbc" class="org.apache.avalon.excalibur.datasource.JdbcDataSource"/&gt;
   *     &lt;hint shorthand="j2ee" class="org.apache.avalon.excalibur.datasource.J2eeDataSource"/&gt;
   *   &lt;/role&gt;
   * </pre>
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class DefaultRoundRobinDataSourceCluster
      extends AbstractDataSourceCluster
      implements RoundRobinDataSourceCluster
  {
      private Object m_semaphore = new Object();
      private int m_nextIndex;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public DefaultRoundRobinDataSourceCluster()
      {
      }
  
      /*---------------------------------------------------------------
       * DataSourceComponent Methods
       *-------------------------------------------------------------*/
      /**
       * Returns a Connection to one of the Cluster's member DataSources.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      public Connection getConnection() throws SQLException
      {
          int index;
          synchronized( m_semaphore )
          {
              index = m_nextIndex;
              if( ( ++m_nextIndex ) >= m_size )
              {
                  m_nextIndex = 0;
              }
          }
  
          return getConnectionForIndex( index );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/HashedDataSourceCluster.java
  
  Index: HashedDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public interface HashedDataSourceCluster
      extends DataSourceComponent
  {
      /**
       * The name of the role for convenience
       */
      String ROLE = "org.apache.avalon.excalibur.datasource.cluster.HashedDataSourceCluster";
  
      /**
       * Returns the number of DataSources in the cluster.
       *
       * @return size of the cluster.
       */
      int getClusterSize();
  
      /**
       * Gets a Connection to a database given a hash object.
       *
       * @param hashObject Object whose hashCode will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      Connection getConnectionForHashObject( Object hashObject ) throws SQLException;
  
      /**
       * Gets a Connection to a database given a hash code.
       *
       * @param hashCode HashCode which will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      Connection getConnectionForHashCode( int hashCode ) throws SQLException;
  
      /**
       * Gets a Connection to a database given an index.
       *
       * @param index Index of the DataSource for which a connection is to be returned.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      Connection getConnectionForIndex( int index ) throws SQLException;
  
      /**
       * Gets the index which will be resolved for a given hashCode.  This can be used
       *  by user code to optimize the use of DataSource Clusters.
       *
       * @param hashObject Object whose hashCode will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       */
      int getIndexForHashObject( Object hashObject );
  
      /**
       * Gets the index which will be resolved for a given hashCode.  This can be used
       *  by user code to optimize the use of DataSource Clusters.
       *
       * @param hashCode HashCode which will be used to select which of the Clusted
       *        DataSources will be provide a Connection.
       */
      int getIndexForHashCode( int hashCode );
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/IndexedDataSourceCluster.java
  
  Index: IndexedDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public interface IndexedDataSourceCluster
      extends DataSourceComponent
  {
      /**
       * The name of the role for convenience
       */
      String ROLE = "com.silveregg.util.datasource.cluster.IndexedDataSourceCluster";
  
      /**
       * Returns the number of DataSources in the cluster.
       *
       * @return size of the cluster.
       */
      int getClusterSize();
  
      /**
       * Gets a Connection to a database given an index.
       *
       * @param index Index of the DataSource for which a connection is to be returned.
       *
       * @throws NoValidConnectionException when there is no valid Connection wrapper
       *         available in the classloader or when the index is not valid.
       *
       * @throws NoAvailableConnectionException when there are no more available
       *         Connections in the pool.
       */
      Connection getConnectionForIndex( int index ) throws SQLException;
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/cluster/RoundRobinDataSourceCluster.java
  
  Index: RoundRobinDataSourceCluster.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.cluster;
  
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public interface RoundRobinDataSourceCluster
      extends DataSourceComponent
  {
      /**
       * The name of the role for convenience
       */
      String ROLE = "org.apache.avalon.excalibur.datasource.cluster.RoundRobinDataSourceCluster";
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/AbstractDataSourceBlockIdGenerator.java
  
  Index: AbstractDataSourceBlockIdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.math.BigDecimal;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  
  /**
   * The AbstractDataSourceBlockIdGenerator allocates blocks of ids from a DataSource
   *  and then provides them as needed.  This is useful in reducing communication with
   *  the DataSource.
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public abstract class AbstractDataSourceBlockIdGenerator
      extends AbstractDataSourceIdGenerator
  {
      /**
       * The first id in a batch of Ids loaded in from the DataSource.
       */
      private BigDecimal m_firstBigDecimal;
  
      /**
       * The first id in a batch of Ids loaded in from the DataSource.
       */
      private long m_firstLong;
  
      /**
       * The number of ids loaded in each block.
       */
      private int m_blockSize;
  
      /**
       * The number of ids which have been allocated from the current block.
       */
      private int m_allocated;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public AbstractDataSourceBlockIdGenerator()
      {
      }
  
      /*---------------------------------------------------------------
       * Methods
       *-------------------------------------------------------------*/
      /**
       * Allocates a block, of the given size, of ids from the database.
       *
       * @param blockSize number of Ids which are to be allocated.
       *
       * @return The first id in the allocated block.
       *
       * @throws IdException if there it was not possible to allocate a block of ids.
       */
      protected abstract BigDecimal allocateBigDecimalIdBlock( int blockSize )
          throws IdException;
  
      /**
       * Allocates a block, of the given size, of ids from the database.
       *
       * @param blockSize number of Ids which are to be allocated.
       *
       * @return The first id in the allocated block.
       *
       * @throws IdException if there it was not possible to allocate a block of ids.
       */
      protected abstract long allocateLongIdBlock( int blockSize )
          throws IdException;
  
      /*---------------------------------------------------------------
       * AbstractIdGenerator Methods
       *-------------------------------------------------------------*/
      /**
       * Gets the next id as a Big Decimal.  This method will only be called
       *  when synchronized and when the data type is configured to be BigDecimal.
       *
       * @return the next id as a BigDecimal.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected BigDecimal getNextBigDecimalIdInner()
          throws IdException
      {
          if( m_allocated >= m_blockSize )
          {
              // Need to allocate a new batch of ids
              try
              {
                  m_firstBigDecimal = allocateBigDecimalIdBlock( m_blockSize );
  
                  // Reset the allocated count
                  m_allocated = 0;
              }
              catch( IdException e )
              {
                  // Set the allocated count to signal that there are not any ids available.
                  m_allocated = Integer.MAX_VALUE;
                  throw e;
              }
          }
  
          // We know that at least one id is available.
          // Get an id out of the currently allocated block.
          BigDecimal id = m_firstBigDecimal.add( new BigDecimal( m_allocated ) );
          m_allocated++;
  
          return id;
      }
  
      /**
       * Gets the next id as a long.  This method will only be called
       *  when synchronized and when the data type is configured to be long.
       *
       * @return the next id as a long.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected long getNextLongIdInner()
          throws IdException
      {
          if( m_allocated >= m_blockSize )
          {
              // Need to allocate a new batch of ids
              try
              {
                  m_firstLong = allocateLongIdBlock( m_blockSize );
  
                  // Reset the allocated count
                  m_allocated = 0;
              }
              catch( IdException e )
              {
                  // Set the allocated count to signal that there are not any ids available.
                  m_allocated = Integer.MAX_VALUE;
                  throw e;
              }
          }
  
          // We know that at least one id is available.
          // Get an id out of the currently allocated block.
          long id = m_firstLong + m_allocated;
          if( id < 0 )
          {
              // The value wrapped
              String msg = "No more Ids are available, the maximum long value has been reached.";
              getLogger().error( msg );
              throw new IdException( msg );
          }
          m_allocated++;
  
          return id;
      }
  
      /*---------------------------------------------------------------
       * Configurable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to configure the component.
       *
       * @param configuration configuration info used to setup the component.
       *
       * @throws ConfigurationException if there are any problems with the configuration.
       */
      public void configure( Configuration configuration )
          throws ConfigurationException
      {
          super.configure( configuration );
  
          // Obtain the block size.
          m_blockSize = configuration.getAttributeAsInteger( "block-size", 10 );
      }
  
      /*---------------------------------------------------------------
       * Initializable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to initialize the component.
       *
       * @throws Exception if there were any problems durring initialization.
       */
      public void initialize()
          throws Exception
      {
          super.initialize();
  
          // Set the state so that the first request for an id will load in a block of ids.
          m_allocated = Integer.MAX_VALUE;
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/AbstractDataSourceIdGenerator.java
  
  Index: AbstractDataSourceIdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.sql.Connection;
  import java.sql.SQLException;
  import java.sql.Statement;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  import org.apache.avalon.framework.activity.Disposable;
  import org.apache.avalon.framework.activity.Initializable;
  import org.apache.avalon.framework.component.ComponentManager;
  import org.apache.avalon.framework.component.ComponentSelector;
  import org.apache.avalon.framework.component.Composable;
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public abstract class AbstractDataSourceIdGenerator
      extends AbstractIdGenerator
      implements IdGenerator, Composable, Configurable, Initializable, Disposable, ThreadSafe
  {
      protected static final int DBTYPE_STANDARD = 0;
      protected static final int DBTYPE_MYSQL = 1;
  
      /** ComponentManager which created this component */
      protected ComponentManager m_manager;
  
      private String m_dataSourceName;
      private ComponentSelector m_dbSelector;
      protected DataSourceComponent m_dataSource;
      protected int m_dbType;
  
      /**
       * Number of allocated Ids remaining before another block must be allocated.
       */
      protected int m_allocated;
      protected long m_nextId;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public AbstractDataSourceIdGenerator()
      {
      }
  
      /*---------------------------------------------------------------
       * Methods
       *-------------------------------------------------------------*/
      /**
       * Allocates a connection for the caller.  The connection must be closed by the caller
       *  when no longer needed.
       *
       * @return an open DB connection.
       *
       * @throws SQLException if the connection can not be obtained for any reason.
       */
      protected Connection getConnection()
          throws SQLException
      {
          return m_dataSource.getConnection();
      }
  
      /*---------------------------------------------------------------
       * Composable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to tell the component which ComponentManager
       *  is controlling it.
       *
       * @param ComponentManager which curently owns the component.
       */
      public void compose( ComponentManager manager )
      {
          m_manager = manager;
      }
  
      /*---------------------------------------------------------------
       * Configurable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to configure the component.
       *
       * @param configuration configuration info used to setup the component.
       *
       * @throws ConfigurationException if there are any problems with the configuration.
       */
      public void configure( Configuration configuration )
          throws ConfigurationException
      {
          // Obtain the big-decimals flag.
          setUseBigDecimals( configuration.getAttributeAsBoolean( "big-decimals", false ) );
  
          // Obtain a reference to the configured DataSource
          m_dataSourceName = configuration.getChild( "dbpool" ).getValue();
      }
  
      /*---------------------------------------------------------------
       * Initializable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to initialize the component.
       *
       * @throws Exception if there were any problems durring initialization.
       */
      public void initialize()
          throws Exception
      {
          // Get a reference to a data source
          m_dbSelector = (ComponentSelector)m_manager.lookup( DataSourceComponent.ROLE + "Selector" );
          m_dataSource = (DataSourceComponent)m_dbSelector.select( m_dataSourceName );
  
          // Resolve the type of database that is being used.
          try
          {
              Connection conn = getConnection();
              try
              {
                  Statement statement = conn.createStatement();
                  String className = statement.getClass().getName();
                  if( className.indexOf( "mysql" ) > 0 )
                  {
                      m_dbType = DBTYPE_MYSQL;
                  }
                  else
                  {
                      m_dbType = DBTYPE_STANDARD;
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to open connection to resolve database type.", e );
          }
      }
  
      /*---------------------------------------------------------------
       * Disposable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to dispose the component.
       */
      public void dispose()
      {
          // Free up the data source
          if( m_dbSelector != null )
          {
              if( m_dataSource != null )
              {
                  m_dbSelector.release( m_dataSource );
  
                  m_dataSource = null;
              }
  
              m_manager.release( m_dbSelector );
  
              m_dbSelector = null;
          }
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/AbstractIdGenerator.java
  
  Index: AbstractIdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.math.BigDecimal;
  import org.apache.avalon.framework.logger.AbstractLogEnabled;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public abstract class AbstractIdGenerator
      extends AbstractLogEnabled
      implements IdGenerator, ThreadSafe
  {
      private static final BigDecimal BIG_DECIMAL_MAX_LONG = new BigDecimal( Long.MAX_VALUE );
  
      /**
       * Used to manage internal synchronization.
       */
      private Object m_semaphore = new Object();
  
      /**
       * Data type for the Id Pool.
       */
      private boolean m_useBigDecimals;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public AbstractIdGenerator()
      {
      }
  
      /*---------------------------------------------------------------
       * Methods
       *-------------------------------------------------------------*/
      /**
       * Gets the next id as a Big Decimal.  This method will only be called
       *  when synchronized and when the data type is configured to be BigDecimal.
       *
       * @return the next id as a BigDecimal.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected abstract BigDecimal getNextBigDecimalIdInner()
          throws IdException;
  
      /**
       * Gets the next id as a long.  This method will only be called
       *  when synchronized and when the data type is configured to be long.
       *
       * @return the next id as a long.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected abstract long getNextLongIdInner()
          throws IdException;
  
      /**
       * By default, the IdGenerator will operate using a backend datatype of type long.  This
       *  is the most efficient, however it does not allow for Ids that are larger than
       *  Long.MAX_VALUE.  To allow very large Ids, it is necessary to make use of the BigDecimal
       *  data storage type.  This method should only be called durring initialization.
       *
       * @param useBigDecimals True to set BigDecimal as the internal data type.
       */
      protected final void setUseBigDecimals( boolean useBigDecimals )
      {
          m_useBigDecimals = useBigDecimals;
      }
  
      /**
       * Returns true if the internal data type is using BigDecimals, false if it is using longs.
       */
      protected final boolean isUsingBigDecimals()
      {
          return m_useBigDecimals;
      }
  
      /**
       * Gets the next Long Id constraining the value to be less than the specified maxId.
       *
       * @throws IdException if the next id is larger than the specified maxId.
       */
      protected final long getNextLongIdChecked( long maxId )
          throws IdException
      {
          long nextId;
          if( m_useBigDecimals )
          {
              // Use BigDecimal data type
              BigDecimal bd;
              synchronized( m_semaphore )
              {
                  bd = getNextBigDecimalIdInner();
              }
  
              // Make sure that the Big Decimal value can be assigned to a long before continuing.
              if( bd.compareTo( BIG_DECIMAL_MAX_LONG ) > 0 )
              {
                  String msg = "Unable to provide an id.  The next id would " +
                      "be greater than the id data type allows.";
                  getLogger().error( msg );
                  throw new IdException( msg );
              }
              nextId = bd.longValue();
          }
          else
          {
              // Use long data type
              synchronized( m_semaphore )
              {
                  nextId = getNextLongIdInner();
              }
          }
  
          // Make sure that the id is valid for the requested data type.
          if( nextId > maxId )
          {
              String msg = "Unable to provide an id.  The next id would " +
                  "be greater than the id data type allows.";
              getLogger().error( msg );
              throw new IdException( msg );
          }
  
          return nextId;
      }
  
      /*---------------------------------------------------------------
       * IdGenerator Methods
       *-------------------------------------------------------------*/
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       */
      public final BigDecimal getNextBigDecimalId()
          throws IdException
      {
          BigDecimal bd;
          if( m_useBigDecimals )
          {
              // Use BigDecimal data type
              synchronized( m_semaphore )
              {
                  bd = getNextBigDecimalIdInner();
              }
          }
          else
          {
              // Use long data type
              synchronized( m_semaphore )
              {
                  bd = new BigDecimal( getNextLongIdInner() );
              }
          }
  
          return bd;
      }
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IdException if the next id is outside of the range of valid longs.
       */
      public final long getNextLongId()
          throws IdException
      {
          return getNextLongIdChecked( Long.MAX_VALUE );
      }
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IdException if the next id is outside of the range of valid integers.
       */
      public final int getNextIntegerId()
          throws IdException
      {
          return (int)getNextLongIdChecked( Integer.MAX_VALUE );
      }
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IdException if the next id is outside of the range of valid shorts.
       */
      public final short getNextShortId()
          throws IdException
      {
          return (short)getNextLongIdChecked( Short.MAX_VALUE );
      }
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IdException if the next id is outside of the range of valid bytes.
       */
      public final byte getNextByteId()
          throws IdException
      {
          return (byte)getNextLongIdChecked( Byte.MAX_VALUE );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/IdException.java
  
  Index: IdException.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import org.apache.avalon.framework.CascadingException;
  
  /**
   * Thrown when it was not possible to allocate an Id.
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class IdException
      extends CascadingException
  {
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      /**
       * Construct a new IdException instance.
       *
       * @param message The detail message for this exception.
       */
      public IdException( String message )
      {
          super( message );
      }
  
      /**
       * Construct a new IdException instance.
       *
       * @param message The detail message for this exception.
       * @param throwable The root cause of the exception.
       */
      public IdException( String message, Throwable throwable )
      {
          super( message, throwable );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/IdGenerator.java
  
  Index: IdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.math.BigDecimal;
  import org.apache.avalon.framework.component.Component;
  
  /**
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public interface IdGenerator
      extends Component
  {
      /**
       * The name of the role for convenience
       */
      String ROLE = "org.apache.avalon.excalibur.datasource.ids.IdGenerator";
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       */
      BigDecimal getNextBigDecimalId()
          throws IdException;
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IllegalStateException if the next id is outside of the range of valid longs.
       */
      long getNextLongId()
          throws IdException;
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IllegalStateException if the next id is outside of the range of valid integers.
       */
      int getNextIntegerId()
          throws IdException;
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IllegalStateException if the next id is outside of the range of valid shorts.
       */
      short getNextShortId()
          throws IdException;
  
      /**
       * Returns the next Id from the pool.
       *
       * @return the next Id.
       *
       * @throws IllegalStateException if the next id is outside of the range of valid bytes.
       */
      byte getNextByteId()
          throws IdException;
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/SequenceIdGenerator.java
  
  Index: SequenceIdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.math.BigDecimal;
  import java.sql.Connection;
  import java.sql.PreparedStatement;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  
  /**
   * The SequenceIdGenerator requests each Id using a sequence in a database.  While not
   *  actually pooling batches of Ids like other IdGenerator implementations, making use of this class
   *  does make code compatable with other IdGenerators on a configuration basis.
   * <p>
   * The Configuration to use a SequenceIdGenerator look like the following:
   * <pre>
   *   &lt;id-generators&gt;
   *       &lt;sequence name="user-ids" logger="cm.ids"&gt;
   *           &lt;dbpool&gt;user-db&lt;/dbpool&gt;
   *           &lt;query&gt;SELECT NEXTVAL('category_ids')&lt;/query&gt;
   *       &lt;/sequence&gt;
   *   &lt;/id-generators&gt;
   * </pre>
   * Where user-db is the name of a DataSource configured in a datasources element, and query is
   *  any query which will return a single id while maintaining state so that successive calls
   *  will continue to return incremented ids.
   * <p>
   *
   * With the following roles declaration:
   * <pre>
   *   &lt;role name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
   *         shorthand="id-generators"
   *         default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *       &lt;hint shorthand="sequence"
   *             class="org.apache.avalon.excalibur.datasource.ids.SequenceIdGenerator"/&gt;
   *   &lt;/role&gt;
   * </pre>
   *
   * To configure your component to use the IdGenerator declared above, its configuration should look
   *  something like the following:
   * <pre>
   *   &lt;user-service logger="cm"&gt;
   *       &lt;dbpool&gt;user-db&lt;/dbpool&gt;
   *       &lt;id-generator&gt;user-ids&lt;/id-generator&gt;
   *   &lt;/user-service&gt;
   * </pre>
   *
   * Your component obtains a reference to an IdGenerator using the same method as it obtains a
   *  DataSource, by making use of a ComponentSelector.
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class SequenceIdGenerator
      extends AbstractDataSourceIdGenerator
  {
      private String m_query;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public SequenceIdGenerator()
      {
      }
  
      /*---------------------------------------------------------------
       * AbstractIdGenerator Methods
       *-------------------------------------------------------------*/
      /**
       * Gets the next id as a Big Decimal.  This method will only be called
       *  when synchronized and when the data type is configured to be BigDecimal.
       *
       * @return the next id as a BigDecimal.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected BigDecimal getNextBigDecimalIdInner()
          throws IdException
      {
          if( getLogger().isDebugEnabled() )
          {
              getLogger().debug( "Requesting an Id using query: " + m_query );
          }
  
          try
          {
              Connection conn = getConnection();
              try
              {
                  PreparedStatement stmt = conn.prepareStatement( m_query );
                  ResultSet rs = stmt.executeQuery();
                  if( rs.next() )
                  {
                      return rs.getBigDecimal( 1 );
                  }
                  else
                  {
                      String msg = "Query for Id did not return a value";
                      getLogger().error( msg );
                      throw new IdException( msg );
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              throw new IdException( "Unable to allocate an Id", e );
          }
      }
  
      /**
       * Gets the next id as a long.  This method will only be called
       *  when synchronized and when the data type is configured to be long.
       *
       * @return the next id as a long.
       *
       * @throws IdException if an Id could not be allocated for any reason.
       */
      protected long getNextLongIdInner()
          throws IdException
      {
          if( getLogger().isDebugEnabled() )
          {
              getLogger().debug( "Requesting an Id using query: " + m_query );
          }
  
          try
          {
              Connection conn = getConnection();
              try
              {
                  PreparedStatement stmt = conn.prepareStatement( m_query );
                  ResultSet rs = stmt.executeQuery();
                  if( rs.next() )
                  {
                      return rs.getLong( 1 );
                  }
                  else
                  {
                      String msg = "Query for Id did not return a value";
                      getLogger().error( msg );
                      throw new IdException( msg );
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              String msg = "Unable to allocate an Id";
              getLogger().error( msg );
              throw new IdException( msg );
          }
      }
  
      /*---------------------------------------------------------------
       * Configurable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to configure the component.
       *
       * @param configuration configuration info used to setup the component.
       *
       * @throws ConfigurationException if there are any problems with the configuration.
       */
      public void configure( Configuration configuration )
          throws ConfigurationException
      {
          super.configure( configuration );
  
          // Obtain the query to use to obtain an id from a sequence.
          m_query = configuration.getChild( "query" ).getValue();
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/java/org/apache/avalon/excalibur/datasource/ids/TableIdGenerator.java
  
  Index: TableIdGenerator.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids;
  
  import java.math.BigDecimal;
  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.Statement;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  
  /**
   * The TableIdGenerator requests blocks of ids from a Database table.  The table consists of two
   *  columns one called <code>table_name</code> of type CHAR or VARCHAR, and the second called
   *  <code>next_id</code> of an integer type large enough to hold your largest ids.
   * <p>
   * The Configuration to use a TableIdGenerator looks like the following:
   * <pre>
   *   &lt;id-generators&gt;
   *       &lt;table name="user-ids" big-decimals="true" block-size="1" table="ids"
   *           key-table="event-type" logger="cm.ids"&gt;
   *           &lt;dbpool&gt;user-db&lt;/dbpool&gt;
   *       &lt;/table&gt;
   *   &lt;/id-generators&gt;
   * </pre>
   * Where user-db is the name of a DataSource configured in a datasources element, block-size is
   *  the number if ids that are allocated with each query to the databse (defaults to "10"),
   *  table is the name of the table which contains the ids (defaults to "ids"), and key-table is
   *  the table_name of the row from which the block of ids are allocated (defaults to "id").
   * <p>
   *
   * With the following roles declaration:
   * <pre>
   *   &lt;role name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
   *         shorthand="id-generators"
   *         default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector"&gt;
   *       &lt;hint shorthand="table"
   *             class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/&gt;
   *   &lt;/role&gt;
   * </pre>
   *
   * To configure your component to use the IdGenerator declared above, its configuration should look
   *  something like the following:
   * <pre>
   *   &lt;user-service logger="cm"&gt;
   *       &lt;dbpool&gt;user-db&lt;/dbpool&gt;
   *       &lt;id-generator&gt;user-ids&lt;/id-generator&gt;
   *   &lt;/user-service&gt;
   * </pre>
   *
   * Your component obtains a reference to an IdGenerator using the same method as it obtains a
   *  DataSource, by making use of a ComponentSelector.
   * <p>
   * Depending on your database, the ids table should look something like the following:
   * <pre>
   *   CREATE TABLE ids (
   *       table_name varchar(16) NOT NULL,
   *       next_id INTEGER NOT NULL,
   *       PRIMARY KEY (table_name)
   *   );
   * </pre>
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   * @version CVS $Revision: 1.1 $ $Date: 2002/04/22 03:04:27 $
   * @since 4.1
   */
  public class TableIdGenerator
      extends AbstractDataSourceBlockIdGenerator
  {
      /**
       * The name of the table containing the ids.
       */
      private String m_table;
  
      /**
       * TableName used to reference which ids to allocate.
       */
      private String m_tableName;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public TableIdGenerator()
      {
      }
  
      /*---------------------------------------------------------------
       * Methods
       *-------------------------------------------------------------*/
      /**
       * Allocates a block of ids of the given size and returns the first id.
       *
       * @param blockSize number of ids to allocate.
       * @param useBigDecimals returns the first id as a BigDecimal if true, otherwise as a Long.
       *
       * @return either a Long or a BigDecimal depending on the value of useBigDecimals
       *
       * @throws IdException if a block of ids can not be allocated.
       */
      private Object allocateIdBlock( int blockSize, boolean useBigDecimals )
          throws IdException
      {
          if( getLogger().isDebugEnabled() )
          {
              getLogger().debug( "Allocating a new block of " + blockSize +
                                 " ids for key_table " + m_tableName + "." );
          }
  
          try
          {
              Connection conn = getConnection();
              try
              {
                  // Turn off auto commit so that we are working in a transaction,
                  //  but keep the old value.
                  boolean oldAutoCommit = conn.getAutoCommit();
                  conn.setAutoCommit( false );
                  try
                  {
                      int oldIsolation = conn.getTransactionIsolation();
                      conn.setTransactionIsolation( Connection.TRANSACTION_SERIALIZABLE );
                      try
                      {
                          try
                          {
                              Statement stmt = conn.createStatement();
  
                              int tries = 0;
                              // May run into conflicts with other processes, so try this up to 50
                              //  times before giving up.
                              while( tries < 50 )
                              {
                                  // Get the nextId from the table
                                  ResultSet rs = stmt.executeQuery(
                                      "SELECT next_id FROM " + m_table + " WHERE table_name = '" + m_tableName + "'" );
                                  if( !rs.next() )
                                  {
                                      // The row does not exist.
                                      String msg = "Unable to allocate a block of Ids, no row with table_name='" +
                                          m_tableName + "' exists in the " + m_table + " table.";
                                      getLogger().error( msg );
                                      conn.rollback();
  
                                      throw new IdException( msg );
                                  }
  
                                  // Get the next_id using the appropriate data type.
                                  Object nextId;
                                  if( useBigDecimals )
                                  {
                                      nextId = rs.getBigDecimal( 1 );
                                  }
                                  else
                                  {
                                      nextId = new Long( rs.getLong( 1 ) );
                                  }
  
                                  // Update the value of next_id in the database so it reflects the full block
                                  //  being allocated.  If another process has done the same thing, then this
                                  //  will throw an exception due to transaction isolation.
                                  try
                                  {
                                      int updated = stmt.executeUpdate( "UPDATE " + m_table +
                                                                        " SET next_id = next_id + " + blockSize +
                                                                        " WHERE table_name = '" + m_tableName + "'" );
                                      if( updated >= 1 )
                                      {
                                          // Update was successful.
                                          conn.commit();
  
                                          // Return the next id obtained above.
                                          return nextId;
                                      }
                                      else
                                      {
                                          // May have been a transaction confict. Try again.
                                          if( getLogger().isDebugEnabled() )
                                          {
                                              getLogger().debug(
                                                  "Update resulted in no rows being changed." );
                                          }
                                      }
                                  }
                                  catch( SQLException e )
                                  {
                                      // Assume that this was caused by a transaction conflict.  Try again.
                                      if( getLogger().isDebugEnabled() )
                                      {
                                          getLogger().debug(
                                              "Encountered an exception attempting to update the " +
                                              m_table + " table.  May be a transaction confict.  " +
                                              "Trying again: " + e.getMessage() );
                                      }
                                  }
  
                                  // If we got here, then we failed, roll back the connection so we can
                                  //  try again.
                                  conn.rollback();
  
                                  tries++;
                              }
                              // If we got here then we ran out of tries.
                              getLogger().error( "Unable to allocate a block of Ids.  Too many retries." );
                              return null;
                          }
                          catch( SQLException e )
                          {
                              // Need this catch so that the connection can be rolled back before
                              //  the transaction is set in the finally block.
                              String msg = "Unable to allocate a block of Ids.";
                              getLogger().error( msg, e );
  
                              // Rollback after the error is logged so that any problems rolling back
                              //  will not prevent the error from being logged.
                              conn.rollback();
  
                              throw new IdException( msg, e );
                          }
                      }
                      finally
                      {
                          // Restore the isolation level
                          conn.setTransactionIsolation( oldIsolation );
                      }
                  }
                  finally
                  {
                      // restore Auto commit
                      conn.setAutoCommit( oldAutoCommit );
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              String msg = "Unable to allocate a block of Ids.";
              getLogger().error( msg, e );
              throw new IdException( msg, e );
          }
      }
  
      /**
       * Allocates a block of ids of the given size and returns the first id.
       *  MySQL does not support transactions so this method handles synchronization
       *  by making use of table locking.
       *
       * @param blockSize number of ids to allocate.
       * @param useBigDecimals returns the first id as a BigDecimal if true, otherwise as a Long.
       *
       * @return either a Long or a BigDecimal depending on the value of useBigDecimals
       *
       * @throws IdException if a block of ids can not be allocated.
       */
      private Object allocateIdBlockMySQL( int blockSize, boolean useBigDecimals )
          throws IdException
      {
          if( getLogger().isDebugEnabled() )
          {
              getLogger().debug( "Allocating a new block of " + blockSize + " ids." );
          }
  
          try
          {
              Connection conn = getConnection();
              try
              {
                  Statement stmt = conn.createStatement();
  
                  // Obtain a lock on the table
                  stmt.executeUpdate( "LOCK TABLES " + m_table + " WRITE" );
                  try
                  {
                      // Get the nextId from the table
                      ResultSet rs = stmt.executeQuery(
                          "SELECT next_id FROM " + m_table + " WHERE table_name = '" + m_tableName + "'" );
                      if( !rs.next() )
                      {
                          // The row does not exist.
                          String msg = "Unable to allocate a block of Ids, no row with table_name='" +
                              m_tableName + "' exists in the " + m_table + " table.";
                          getLogger().error( msg );
  
                          throw new IdException( msg );
                      }
  
                      // Get the next_id using the appropriate data type.
                      Object nextId;
                      Object nextSavedId;
                      if( useBigDecimals )
                      {
                          BigDecimal id = rs.getBigDecimal( 1 );
                          nextId = id;
                          nextSavedId = id.add( new BigDecimal( blockSize ) );
                      }
                      else
                      {
                          Long id = new Long( rs.getLong( 1 ) );
                          nextId = id;
                          nextSavedId = new Long( id.longValue() + blockSize );
                      }
  
                      // Update the value of next_id in the database so it reflects the full block
                      //  being allocated.
                      //
                      // Queries that set next_id = next_id + 10 do not work with large decimal values on MySQL 3.23.31
                      int updated = stmt.executeUpdate( "UPDATE " + m_table +
                                                        " SET next_id = " + nextSavedId +
                                                        " WHERE table_name = '" + m_tableName + "'" );
                      if( updated >= 1 )
                      {
                          // Return the next id obtained above.
                          return nextId;
                      }
                      else
                      {
                          String msg = "Update resulted in no rows being changed.";
                          getLogger().error( msg );
  
                          throw new IdException( msg );
                      }
                  }
                  finally
                  {
                      // Make absolutely sure that the lock is always released.
                      stmt.executeUpdate( "UNLOCK TABLES" );
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              String msg = "Unable to allocate a block of Ids.";
              getLogger().error( msg, e );
              throw new IdException( msg, e );
          }
      }
  
      /*---------------------------------------------------------------
       * AbstractDataSourceBlockIdGenerator Methods
       *-------------------------------------------------------------*/
      /**
       * Allocates a block, of the given size, of ids from the database.
       *
       * @param blockSize number of Ids which are to be allocated.
       *
       * @return The first id in the allocated block.
       *
       * @throws IdException if there it was not possible to allocate a block of ids.
       */
      protected BigDecimal allocateBigDecimalIdBlock( int blockSize )
          throws IdException
      {
          BigDecimal id;
          switch( m_dbType )
          {
              case AbstractDataSourceIdGenerator.DBTYPE_MYSQL:
                  id = (BigDecimal)allocateIdBlockMySQL( blockSize, true );
                  break;
  
              default:
                  id = (BigDecimal)allocateIdBlock( blockSize, true );
          }
  
          return id;
      }
  
      /**
       * Allocates a block, of the given size, of ids from the database.
       *
       * @param blockSize number of Ids which are to be allocated.
       *
       * @return The first id in the allocated block.
       *
       * @throws IdException if there it was not possible to allocate a block of ids.
       */
      protected long allocateLongIdBlock( int blockSize )
          throws IdException
      {
          Long id;
          switch( m_dbType )
          {
              case AbstractDataSourceIdGenerator.DBTYPE_MYSQL:
                  id = (Long)allocateIdBlockMySQL( blockSize, false );
                  break;
  
              default:
                  id = (Long)allocateIdBlock( blockSize, false );
          }
  
          return id.longValue();
      }
  
      /*---------------------------------------------------------------
       * Configurable Methods
       *-------------------------------------------------------------*/
      /**
       * Called by the Container to configure the component.
       *
       * @param configuration configuration info used to setup the component.
       *
       * @throws ConfigurationException if there are any problems with the configuration.
       */
      public void configure( Configuration configuration )
          throws ConfigurationException
      {
          super.configure( configuration );
  
          // Obtain the table name.
          m_table = configuration.getAttribute( "table", "ids" );
  
          // Obtain the key-table.
          m_tableName = configuration.getAttribute( "key-table", "id" );
      }
  }
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorJdbcTestCase.java
  
  Index: TableIdGeneratorJdbcTestCase.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids.test;
  
  import java.math.BigDecimal;
  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.Statement;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  import org.apache.avalon.excalibur.datasource.ids.IdException;
  import org.apache.avalon.excalibur.datasource.ids.IdGenerator;
  import org.apache.avalon.excalibur.testcase.ExcaliburTestCase;
  import org.apache.avalon.framework.component.ComponentSelector;
  
  /**
   * Test the TableIdGenerator Component.
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   */
  public class TableIdGeneratorJdbcTestCase
      extends ExcaliburTestCase
  {
      private ComponentSelector m_dbSelector;
      private DataSourceComponent m_dataSource;
  
      private ComponentSelector m_idGeneratorSelector;
      private IdGenerator m_idGenerator;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public TableIdGeneratorJdbcTestCase( String name )
      {
          super( name );
  
          // Set the priority for default log output.
          m_logPriority = org.apache.log.Priority.INFO;
      }
  
      /*---------------------------------------------------------------
       * TestCase Methods
       *-------------------------------------------------------------*/
      public void setUp() throws Exception
      {
          super.setUp();
  
          // Get a reference to a data source
          m_dbSelector = (ComponentSelector)manager.lookup( DataSourceComponent.ROLE + "Selector" );
          m_dataSource = (DataSourceComponent)m_dbSelector.select( "test-db" );
  
          // We need to initialize an ids table in the database for these tests.
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  // Try to drop the table.  It may not exist and throw an exception.
                  getLogger().debug( "Attempting to drop old ids table" );
                  try
                  {
                      statement.executeUpdate( "DROP TABLE ids" );
                  }
                  catch( SQLException e )
                  {
                      // The table was probably just not there.  Ignore this.
                  }
  
                  // Create the table that we will use in this test.
                  // Different depending on the db. Please add new statements as new databases are
                  //  tested.
                  getLogger().debug( "Create new ids table" );
                  statement.executeUpdate(
                      "CREATE TABLE ids ( " +
                      "table_name varchar(16) NOT NULL, " +
                      "next_id DECIMAL(30) NOT NULL, " +
                      "PRIMARY KEY (table_name))" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to initialize database for test.", e );
              fail( "Unable to initialize database for test. " + e );
          }
  
          // Get a reference to an IdGenerator Selector.
          // Individual IdGenerators are obtained in the tests.
          m_idGeneratorSelector = (ComponentSelector)manager.lookup( IdGenerator.ROLE + "Selector" );
  
      }
  
      public void tearDown() throws Exception
      {
          // Free up the IdGenerator Selector
          if( m_idGeneratorSelector != null )
          {
              manager.release( m_idGeneratorSelector );
  
              m_dbSelector = null;
          }
  
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  // Delete the table that we will use in this test.
                  getLogger().debug( "Drop ids table" );
                  statement.executeUpdate( "DROP TABLE ids" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to cleanup database after test.", e );
              // Want to continue
          }
  
          // Free up the data source
          if( m_dbSelector != null )
          {
              if( m_dataSource != null )
              {
                  m_dbSelector.release( m_dataSource );
  
                  m_dataSource = null;
              }
  
              manager.release( m_dbSelector );
  
              m_dbSelector = null;
          }
  
          super.tearDown();
      }
  
      /*---------------------------------------------------------------
       * Test Cases
       *-------------------------------------------------------------*/
      public void testNonExistingTableName() throws Exception
      {
          getLogger().info( "testNonExistingTableName" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testNonExistingTableName" );
          try
          {
              try
              {
                  int id = idGenerator.getNextIntegerId();
                  fail( "Should not have gotten an id" );
              }
              catch( IdException e )
              {
                  // Got the expected error.
              }
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testSimpleRequestIdsSize1() throws Exception
      {
          getLogger().info( "testSimpleRequestIdsSize1" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize1" );
          try
          {
              int testCount = 100;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", 1 );
  
              for( int i = 1; i <= testCount; i++ )
              {
                  int id = idGenerator.getNextIntegerId();
                  assertEquals( "The returned id was not what was expected.", i, id );
              }
  
              assertEquals( "The next_id column in the database did not have the expected value.",
                            testCount + 1, peekNextLongId( "test" ) );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testSimpleRequestIdsSize10() throws Exception
      {
          getLogger().info( "testSimpleRequestIdsSize10" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize10" );
          try
          {
              int testCount = 100;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", 1 );
  
              for( int i = 1; i <= testCount; i++ )
              {
                  int id = idGenerator.getNextIntegerId();
                  assertEquals( "The returned id was not what was expected.", i, id );
              }
  
              assertEquals( "The next_id column in the database did not have the expected value.",
                            testCount + 1, peekNextLongId( "test" ) );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testSimpleRequestIdsSize100() throws Exception
      {
          getLogger().info( "testSimpleRequestIdsSize100" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize100" );
          try
          {
              int testCount = 100;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", 1 );
  
              for( int i = 1; i <= testCount; i++ )
              {
                  int id = idGenerator.getNextIntegerId();
                  assertEquals( "The returned id was not what was expected.", i, id );
              }
  
              assertEquals( "The next_id column in the database did not have the expected value.",
                            testCount + 1, peekNextLongId( "test" ) );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testBigDecimalRequestIdsSize10() throws Exception
      {
          getLogger().info( "testBigDecimalRequestIdsSize10" );
  
          if( isBigDecimalImplemented() )
          {
              IdGenerator idGenerator =
                  (IdGenerator)m_idGeneratorSelector.select( "ids-testBigDecimalRequestIdsSize10" );
              try
              {
                  int testCount = 100;
                  BigDecimal initial = new BigDecimal( Long.MAX_VALUE + "00" );
  
                  // Initialize the counter in the database.
                  initializeNextBigDecimalId( "test", initial );
  
                  for( int i = 0; i < testCount; i++ )
                  {
                      BigDecimal id = idGenerator.getNextBigDecimalId();
                      assertEquals( "The returned id was not what was expected.",
                                    initial.add( new BigDecimal( i ) ), id );
                  }
  
                  assertEquals( "The next_id column in the database did not have the expected value.",
                                initial.add( new BigDecimal( testCount ) ), peekNextBigDecimalId( "test" ) );
              }
              finally
              {
                  m_idGeneratorSelector.release( idGenerator );
              }
          }
          else
          {
              getLogger().warn( "Test Skipped because BigDecimals are not implemented in current driver." );
          }
      }
  
      public void testMaxByteIds() throws Exception
      {
          getLogger().info( "testMaxByteIds" );
  
          IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select( "ids-testMaxByteIds" );
          try
          {
              int testCount = 100;
              long max = Byte.MAX_VALUE;
              long initial = max - testCount;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", initial );
  
              for( int i = 0; i <= testCount; i++ )
              {
                  byte id = idGenerator.getNextByteId();
                  assertEquals( "The returned id was not what was expected.", i + initial, id );
              }
  
              // Next one should throw an exception
              try
              {
                  byte id = idGenerator.getNextByteId();
                  fail( "Should not have gotten an id: " + id );
              }
              catch( IdException e )
              {
                  // Good.  Got the exception.
              }
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testMaxShortIds() throws Exception
      {
          getLogger().info( "testMaxShortIds" );
  
          IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select( "ids-testMaxShortIds" );
          try
          {
              int testCount = 100;
              long max = Short.MAX_VALUE;
              long initial = max - testCount;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", initial );
  
              for( int i = 0; i <= testCount; i++ )
              {
                  short id = idGenerator.getNextShortId();
                  assertEquals( "The returned id was not what was expected.", i + initial, id );
              }
  
              // Next one should throw an exception
              try
              {
                  short id = idGenerator.getNextShortId();
                  fail( "Should not have gotten an id: " + id );
              }
              catch( IdException e )
              {
                  // Good.  Got the exception.
              }
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testMaxIntegerIds() throws Exception
      {
          getLogger().info( "testMaxIntegerIds" );
  
          IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select( "ids-testMaxIntegerIds" );
          try
          {
              int testCount = 100;
              long max = Integer.MAX_VALUE;
              long initial = max - testCount;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", initial );
  
              for( int i = 0; i <= testCount; i++ )
              {
                  int id = idGenerator.getNextIntegerId();
                  assertEquals( "The returned id was not what was expected.", i + initial, id );
              }
  
              // Next one should throw an exception
              try
              {
                  int id = idGenerator.getNextIntegerId();
                  fail( "Should not have gotten an id: " + id );
              }
              catch( IdException e )
              {
                  // Good.  Got the exception.
              }
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testMaxLongIds() throws Exception
      {
          getLogger().info( "testMaxLongIds" );
  
          IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select( "ids-testMaxLongIds" );
          try
          {
              int testCount = 100;
              long max = Long.MAX_VALUE;
              long initial = max - testCount;
  
              // Initialize the counter in the database.
              initializeNextLongId( "test", initial );
  
              for( int i = 0; i <= testCount; i++ )
              {
                  long id = idGenerator.getNextLongId();
                  assertEquals( "The returned id was not what was expected.", i + initial, id );
              }
  
              // Next one should throw an exception
              try
              {
                  long id = idGenerator.getNextLongId();
                  fail( "Should not have gotten an id: " + id );
              }
              catch( IdException e )
              {
                  // Good.  Got the exception.
              }
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      /*---------------------------------------------------------------
       * Utilitity Methods
       *-------------------------------------------------------------*/
      /**
       * Tests to see whether or not the current DataSource supports BigDecimal
       */
      private boolean isBigDecimalImplemented()
      {
          String tableName = "foorbar_table";
  
          // Add a row that can be selected.
          initializeNextLongId( tableName, 1 );
  
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      BigDecimal id = rs.getBigDecimal( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return false; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
  
              // Implemented
              return true;
          }
          catch( SQLException e )
          {
              if( e.toString().toLowerCase().indexOf( "implemented" ) > 0 )
              {
                  // Not implemented
                  return false;
              }
              getLogEnabledLogger().error( "Unable to test for BigDecimal support.", e );
              fail( "Unable to test for BigDecimal support. " + e );
              return false; // for compiler
          }
      }
  
      private void initializeNextBigDecimalId( String tableName, BigDecimal nextId )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  statement.executeUpdate( "INSERT INTO ids (table_name, next_id) VALUES ('" +
                                           tableName + "', " + nextId.toString() + ")" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to initialize next_id.", e );
              fail( "Unable to initialize next_id. " + e );
          }
      }
  
      private void initializeNextLongId( String tableName, long nextId )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  statement.executeUpdate( "INSERT INTO ids (table_name, next_id) VALUES ('" +
                                           tableName + "', " + nextId + ")" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to initialize next_id.", e );
              fail( "Unable to initialize next_id. " + e );
          }
      }
  
      private BigDecimal peekNextBigDecimalId( String tableName )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      return rs.getBigDecimal( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return null; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to peek next_id.", e );
              fail( "Unable to peek next_id. " + e );
              return null; // for compiler
          }
      }
  
      private long peekNextLongId( String tableName )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      return rs.getLong( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return -1; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogger().error( "Unable to peek next_id.", e );
              fail( "Unable to peek next_id. " + e );
              return -1; // for compiler
          }
      }
  }
  
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorJdbcTestCase.xtest
  
  Index: TableIdGeneratorJdbcTestCase.xtest
  ===================================================================
  <testcase>
      <annotation>
          <![CDATA[
          <title>TableIdGenerator Tests</title>
          <para>
          This series of tests excersizes the TableIdGenerator provided by Excalibur.
          The configuration is specified in the file located in
          <parameter>jakarta-avalon-excalibur/src/scratchpad/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorJdbcTestCase.xtext</parameter>.
          </para>
          ]]>
      </annotation>
      
      <!-- =================================================================== -->
      <!-- LogKit Configuration.                                               -->
      <!-- =================================================================== -->
      <logkit>
          <factories>
              <factory type="stream" 
                  class="org.apache.avalon.excalibur.logger.factory.StreamTargetFactory"/>
              <factory type="file" class="org.apache.avalon.excalibur.logger.factory.FileTargetFactory"/>
          </factories>
          
          <targets>
              <stream id="console">
                  <stream>System.out</stream>
                  <format type="avalon">
                      %7.7{priority} %5.5{time}   [%8.8{category}] (%{context}): %{message}\n%{throwable}
                  </format>
              </stream>
              <file id="file">
                  <filename>TEST-org.apache.avalon.excalibur.datasource.ids.test.TableIdGeneratorJdbcTestCase.log</filename>
                  <format type="extended">
                      %7.7{priority} %5.5{time}   [%8.8{category}] (%{context}): %{message}\n%{throwable}
                  </format>
              </file>
          </targets>
          
          <categories>
              <category name="jdbc" log-level="INFO">
                  <log-target id-ref="console"/>
                  <log-target id-ref="file"/>
              </category>
              
              <category name="id-gen" log-level="INFO">
                  <log-target id-ref="console"/>
                  <log-target id-ref="file"/>
              </category>
          </categories>
      </logkit>
      
      <!-- =================================================================== -->
      <!-- Roles Configuration.                                                -->
      <!-- =================================================================== -->
      <roles>
          <role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
                shorthand="datasources"
                default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
              
              <hint shorthand="jdbc"
                  class="org.apache.avalon.excalibur.datasource.ResourceLimitingJdbcDataSource"/>
          </role>
          
          <role name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
                shorthand="id-generators"
                default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
              <hint shorthand="table"
                  class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/>
          </role>
          <role name="org.apache.avalon.excalibur.datasource.ids.IdGenerator"
              shorthand="id-generator"
              default-class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/>
      </roles>
      
      <!-- =================================================================== -->
      <!-- Component Configuration.                                            -->
      <!-- =================================================================== -->
      <components>
          <datasources>
              <jdbc name="test-db" logger="jdbc">
                  <pool-controller min="1" max="10"/>
                  <auto-commit>true</auto-commit>
                  <driver>@test.jdbc.driver@</driver>
                  <dburl>@test.jdbc.url@</dburl>
                  <user>@test.jdbc.user@</user>
                  <password>@test.jdbc.password@</password>
              </jdbc>
          </datasources>
          
          <id-generators>
              <table name="ids-testNonExistingTableName" block-size="1" table="ids"
                     key-table="does-not-exist" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize1" block-size="1" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize10" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize100" block-size="100" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testBigDecimalRequestIdsSize10" big-decimals="true"
                     block-size="10" table="ids" key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxByteIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxShortIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxIntegerIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxLongIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
          </id-generators>
      </components>
  </testcase>
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorMultithreadedJdbcTestCase.java
  
  Index: TableIdGeneratorMultithreadedJdbcTestCase.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE.txt file.
   */
  package org.apache.avalon.excalibur.datasource.ids.test;
  
  import java.math.BigDecimal;
  import java.sql.Connection;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.sql.Statement;
  import java.util.HashMap;
  import org.apache.avalon.excalibur.datasource.DataSourceComponent;
  import org.apache.avalon.excalibur.datasource.ids.IdGenerator;
  import org.apache.avalon.excalibur.testcase.CascadingAssertionFailedError;
  import org.apache.avalon.excalibur.testcase.ExcaliburTestCase;
  import org.apache.avalon.excalibur.testcase.LatchedThreadGroup;
  import org.apache.avalon.framework.component.ComponentSelector;
  
  /**
   * Test the TableIdGenerator Component.
   *
   * @author <a href="mailto:leif@tanukisoftware.com">Leif Mortenson</a>
   */
  public class TableIdGeneratorMultithreadedJdbcTestCase
      extends ExcaliburTestCase
  {
      private static final String TABLE_KEY = "test";
      private static final int ID_COUNT = 1000;
      private static final int THREAD_COUNT = 50;
  
      private ComponentSelector m_dbSelector;
      private DataSourceComponent m_dataSource;
  
      private ComponentSelector m_idGeneratorSelector;
  
      private Object m_semaphore = new Object();
      private IdGenerator m_idGenerator;
      private int m_perThreadGets;
      private HashMap m_ids;
      private Throwable m_throwable;
  
      /*---------------------------------------------------------------
       * Constructors
       *-------------------------------------------------------------*/
      public TableIdGeneratorMultithreadedJdbcTestCase( String name )
      {
          super( name );
  
          // Set the priority for default log output.
          m_logPriority = org.apache.log.Priority.INFO;
      }
  
      /*---------------------------------------------------------------
       * TestCase Methods
       *-------------------------------------------------------------*/
      public void setUp() throws Exception
      {
          super.setUp();
  
          // Get a reference to a data source
          m_dbSelector = (ComponentSelector)manager.lookup( DataSourceComponent.ROLE + "Selector" );
          m_dataSource = (DataSourceComponent)m_dbSelector.select( "test-db" );
  
          // We need to initialize an ids table in the database for these tests.
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  // Try to drop the table.  It may not exist and throw an exception.
                  getLogEnabledLogger().debug( "Attempting to drop old ids table" );
                  try
                  {
                      statement.executeUpdate( "DROP TABLE ids" );
                  }
                  catch( SQLException e )
                  {
                      // The table was probably just not there.  Ignore this.
                  }
  
                  // Create the table that we will use in this test.
                  // Different depending on the db. Please add new statements as new databases are
                  //  tested.
                  getLogEnabledLogger().debug( "Create new ids table" );
                  statement.executeUpdate(
                      "CREATE TABLE ids ( " +
                      "table_name varchar(16) NOT NULL, " +
                      "next_id DECIMAL(30) NOT NULL, " +
                      "PRIMARY KEY (table_name))" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to initialize database for test.", e );
              fail( "Unable to initialize database for test. " + e );
          }
  
          // Get a reference to an IdGenerator Selector.
          // Individual IdGenerators are obtained in the tests.
          m_idGeneratorSelector = (ComponentSelector)manager.lookup( IdGenerator.ROLE + "Selector" );
  
      }
  
      public void tearDown() throws Exception
      {
          // Free up the IdGenerator Selector
          if( m_idGeneratorSelector != null )
          {
              manager.release( m_idGeneratorSelector );
  
              m_dbSelector = null;
          }
  
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  // Delete the table that we will use in this test.
                  getLogEnabledLogger().debug( "Drop ids table" );
                  statement.executeUpdate( "DROP TABLE ids" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to cleanup database after test.", e );
              // Want to continue
          }
  
          // Free up the data source
          if( m_dbSelector != null )
          {
              if( m_dataSource != null )
              {
                  m_dbSelector.release( m_dataSource );
  
                  m_dataSource = null;
              }
  
              manager.release( m_dbSelector );
  
              m_dbSelector = null;
          }
  
          super.tearDown();
      }
  
      /*---------------------------------------------------------------
       * Test Cases
       *-------------------------------------------------------------*/
      public void testSimpleRequestIdsSize1() throws Exception
      {
          getLogEnabledLogger().info( "testSimpleRequestIdsSize1" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize1" );
          try
          {
              long firstId = 1;
              int idCount = ID_COUNT;
              int threadCount = THREAD_COUNT;
  
              // Initialize the counter in the database.
              initializeNextLongId( TABLE_KEY, firstId );
  
              generalTestCase( idGenerator, firstId, idCount, threadCount );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testSimpleRequestIdsSize10() throws Exception
      {
          getLogEnabledLogger().info( "testSimpleRequestIdsSize10" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize10" );
          try
          {
              long firstId = 1;
              int idCount = ID_COUNT;
              int threadCount = THREAD_COUNT;
  
              // Initialize the counter in the database.
              initializeNextLongId( TABLE_KEY, firstId );
  
              generalTestCase( idGenerator, firstId, idCount, threadCount );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testSimpleRequestIdsSize100() throws Exception
      {
          getLogEnabledLogger().info( "testSimpleRequestIdsSize100" );
  
          IdGenerator idGenerator =
              (IdGenerator)m_idGeneratorSelector.select( "ids-testSimpleRequestIdsSize100" );
          try
          {
              long firstId = 1;
              int idCount = ID_COUNT;
              int threadCount = THREAD_COUNT;
  
              // Initialize the counter in the database.
              initializeNextLongId( TABLE_KEY, firstId );
  
              generalTestCase( idGenerator, firstId, idCount, threadCount );
          }
          finally
          {
              m_idGeneratorSelector.release( idGenerator );
          }
      }
  
      public void testBigDecimalRequestIdsSize10() throws Exception
      {
          getLogEnabledLogger().info( "testBigDecimalRequestIdsSize10" );
  
          if( isBigDecimalImplemented() )
          {
              IdGenerator idGenerator =
                  (IdGenerator)m_idGeneratorSelector.select( "ids-testBigDecimalRequestIdsSize10" );
              try
              {
                  long firstId = 1;
                  int idCount = ID_COUNT;
                  int threadCount = THREAD_COUNT;
  
                  // Initialize the counter in the database.
                  initializeNextLongId( TABLE_KEY, firstId );
  
                  generalTestCase( idGenerator, firstId, idCount, threadCount );
              }
              finally
              {
                  m_idGeneratorSelector.release( idGenerator );
              }
          }
          else
          {
              getLogEnabledLogger().warn( "Test Skipped because BigDecimals are not implemented in current driver." );
          }
      }
  
      /*---------------------------------------------------------------
       * Utilitity Methods
       *-------------------------------------------------------------*/
      /**
       * General multithreaded test of an IdGenerator
       *
       * @param idGenerator the Id Generator to test.
       * @param firstId the first Id that is expected to be returned by the Id Generator.
       * @param idCount the number of ids to request in the test.
       * @param threadCount the number of threads to use to test the Id Generator.
       */
      private void generalTestCase( final IdGenerator idGenerator,
                                    final long firstId,
                                    final int idCount,
                                    final int threadCount )
      {
          if( idCount % threadCount != 0 )
          {
              fail( "idCount must be evenly divisible by threadCount" );
          }
  
          m_idGenerator = idGenerator;
          m_perThreadGets = idCount / threadCount;
          m_ids = new HashMap();
  
          // Create the runnable which will be used by the test.
          Runnable runnable = new Runnable()
          {
              public void run()
              {
                  boolean duplicatesFound = false;
  
                  for( int i = 0; i < m_perThreadGets; i++ )
                  {
                      try
                      {
                          long id = m_idGenerator.getNextLongId();
  
                          synchronized( m_semaphore )
                          {
                              Long lId = new Long( id );
  
                              // Make sure this id has not already been seen
                              if( m_ids.get( lId ) != null )
                              {
                                  getLogEnabledLogger().error( "Obtained a duplicate id: " + id );
                                  duplicatesFound = true;
                              }
                              else
                              {
                                  // Store a reference to this id
                                  m_ids.put( lId, lId );
                              }
                          }
                      }
                      catch( Throwable t )
                      {
                          synchronized( m_semaphore )
                          {
                              if( m_throwable == null )
                              {
                                  m_throwable = t;
                              }
                          }
                          return;
                      }
                  }
  
                  if( duplicatesFound )
                  {
                      fail( "IdGenerator returned duplicate ids." );
                  }
              }
          };
  
          LatchedThreadGroup group = new LatchedThreadGroup( runnable, threadCount );
          group.enableLogging( getLogEnabledLogger() );
  
          // Run the test.
          long duration;
          try
          {
              duration = group.go();
          }
          catch( Throwable t )
          {
              // Throwable could have been thrown by one of the tests.
              if( m_throwable == null )
              {
                  m_throwable = t;
              }
              duration = 0;
          }
  
          if( m_throwable != null )
          {
              throw new CascadingAssertionFailedError( "Exception in test thread.", m_throwable );
          }
  
          // Make sure that all of the expected ids were obtained
          for( int i = 0; i < idCount; i++ )
          {
              Long id = new Long( firstId + i );
              assertTrue( "The IdGenerator did not return an expected id (" + id + ")",
                          m_ids.get( id ) != null );
          }
  
          getLogEnabledLogger().info( "It took " + duration + "ms. for " + threadCount +
                                      " threads to allocate " + idCount + " ids." );
  
          assertEquals( "The next_id column in the database did not have the expected value.",
                        firstId + idCount, peekNextLongId( TABLE_KEY ) );
      }
  
      /**
       * Tests to see whether or not the current DataSource supports BigDecimal
       */
      private boolean isBigDecimalImplemented()
      {
          String tableName = "foorbar_table";
  
          // Add a row that can be selected.
          initializeNextLongId( tableName, 1 );
  
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      BigDecimal id = rs.getBigDecimal( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return false; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
  
              // Implemented
              return true;
          }
          catch( SQLException e )
          {
              if( e.toString().toLowerCase().indexOf( "implemented" ) > 0 )
              {
                  // Not implemented
                  return false;
              }
              getLogEnabledLogger().error( "Unable to test for BigDecimal support.", e );
              fail( "Unable to test for BigDecimal support. " + e );
              return false; // for compiler
          }
      }
  
      private void initializeNextBigDecimalId( String tableName, BigDecimal nextId )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  statement.executeUpdate( "INSERT INTO ids (table_name, next_id) VALUES ('" +
                                           tableName + "', " + nextId.toString() + ")" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to initialize next_id.", e );
              fail( "Unable to initialize next_id. " + e );
          }
      }
  
      private void initializeNextLongId( String tableName, long nextId )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  statement.executeUpdate( "INSERT INTO ids (table_name, next_id) VALUES ('" +
                                           tableName + "', " + nextId + ")" );
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to initialize next_id.", e );
              fail( "Unable to initialize next_id. " + e );
          }
      }
  
      private BigDecimal peekNextBigDecimalId( String tableName )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      return rs.getBigDecimal( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return null; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to peek next_id.", e );
              fail( "Unable to peek next_id. " + e );
              return null; // for compiler
          }
      }
  
      private long peekNextLongId( String tableName )
      {
          try
          {
              Connection conn = m_dataSource.getConnection();
              try
              {
                  Statement statement = conn.createStatement();
  
                  ResultSet rs = statement.executeQuery( "SELECT next_id FROM ids " +
                                                         "WHERE table_name = '" + tableName + "'" );
                  if( rs.next() )
                  {
                      return rs.getLong( 1 );
                  }
                  else
                  {
                      fail( tableName + " row not in ids table." );
                      return -1; // for compiler
                  }
              }
              finally
              {
                  conn.close();
              }
          }
          catch( SQLException e )
          {
              getLogEnabledLogger().error( "Unable to peek next_id.", e );
              fail( "Unable to peek next_id. " + e );
              return -1; // for compiler
          }
      }
  }
  
  
  
  
  1.1                  jakarta-avalon-excalibur/datasource/src/test/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorMultithreadedJdbcTestCase.xtest
  
  Index: TableIdGeneratorMultithreadedJdbcTestCase.xtest
  ===================================================================
  <testcase>
      <annotation>
          <![CDATA[
          <title>TableIdGenerator Tests</title>
          <para>
          This series of tests excersizes the TableIdGenerator provided by Excalibur in a multithreaded context.
          The configuration is specified in the file located in
          <parameter>jakarta-avalon-excalibur/src/scratchpad/org/apache/avalon/excalibur/datasource/ids/test/TableIdGeneratorMultithreadedJdbcTestCase.xtext</parameter>.
          </para>
          ]]>
      </annotation>
      
      <!-- =================================================================== -->
      <!-- LogKit Configuration.                                               -->
      <!-- =================================================================== -->
      <logkit>
          <factories>
              <factory type="stream" 
                  class="org.apache.avalon.excalibur.logger.factory.StreamTargetFactory"/>
              <factory type="file" class="org.apache.avalon.excalibur.logger.factory.FileTargetFactory"/>
          </factories>
          
          <targets>
              <stream id="console">
                  <stream>System.out</stream>
                  <format type="avalon">
                      %7.7{priority} %5.5{time}   [%8.8{category}] (%{context}): %{message}\n%{throwable}
                  </format>
              </stream>
              <file id="file">
                  <filename>TEST-org.apache.avalon.excalibur.datasource.ids.test.TableIdGeneratorMultithreadedJdbcTestCase.log</filename>
                  <format type="extended">
                      %7.7{priority} %5.5{time}   [%8.8{category}] (%{context}): %{message}\n%{throwable}
                  </format>
              </file>
          </targets>
          
          <categories>
              <category name="jdbc" log-level="INFO">
                  <log-target id-ref="console"/>
                  <log-target id-ref="file"/>
              </category>
              
              <category name="id-gen" log-level="INFO">
                  <log-target id-ref="console"/>
                  <log-target id-ref="file"/>
              </category>
          </categories>
      </logkit>
      
      <!-- =================================================================== -->
      <!-- Roles Configuration.                                                -->
      <!-- =================================================================== -->
      <roles>
          <role name="org.apache.avalon.excalibur.datasource.DataSourceComponentSelector"
                shorthand="datasources"
                default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
              
              <hint shorthand="jdbc"
                  class="org.apache.avalon.excalibur.datasource.ResourceLimitingJdbcDataSource"/>
          </role>
          
          <role name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
                shorthand="id-generators"
                default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
              <hint shorthand="table"
                  class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/>
          </role>
          <role name="org.apache.avalon.excalibur.datasource.ids.IdGenerator"
              shorthand="id-generator"
              default-class="org.apache.avalon.excalibur.datasource.ids.TableIdGenerator"/>
      </roles>
      
      <!-- =================================================================== -->
      <!-- Component Configuration.                                            -->
      <!-- =================================================================== -->
      <components>
          <datasources>
              <jdbc name="test-db" logger="jdbc">
                  <pool-controller min="1" max="10"/>
                  <auto-commit>true</auto-commit>
                  <driver>@test.jdbc.driver@</driver>
                  <dburl>@test.jdbc.url@</dburl>
                  <user>@test.jdbc.user@</user>
                  <password>@test.jdbc.password@</password>
              </jdbc>
          </datasources>
          
          <id-generators>
              <table name="ids-testNonExistingTableName" block-size="1" table="ids"
                     key-table="does-not-exist" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize1" block-size="1" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize10" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testSimpleRequestIdsSize100" block-size="100" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testBigDecimalRequestIdsSize10" big-decimals="true"
                     block-size="10" table="ids" key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxByteIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxShortIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxIntegerIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
              
              <table name="ids-testMaxLongIds" block-size="10" table="ids"
                     key-table="test" logger="id-gen">
                  <dbpool>test-db</dbpool>
              </table>
          </id-generators>
      </components>
  </testcase>
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>