leif 02/02/08 04:27:32
Added: src/scratchpad/org/apache/avalon/excalibur/datasource/ids
AbstractDataSourceBlockIdGenerator.java
AbstractDataSourceIdGenerator.java
AbstractIdGenerator.java IdException.java
IdGenerator.java SequenceIdGenerator.java
TableIdGenerator.java
src/scratchpad/org/apache/avalon/excalibur/datasource/ids/test
TableIdGeneratorJdbcTestCase.java
TableIdGeneratorJdbcTestCase.xtest
Log:
Initial stab at IdGenerators
Revision Changes Path
1.1
jakarta-avalon-excalibur/src/scratchpad/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 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 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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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;
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/src/scratchpad/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.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
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.Composable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.ComponentSelector;
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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @since 4.1
*/
public abstract class AbstractDataSourceIdGenerator
extends AbstractIdGenerator
implements IdGenerator, Composable, Configurable, Initializable,
Disposable, ThreadSafe
{
/** ComponentManager which created this component */
protected ComponentManager m_manager;
private String m_dataSourceName;
private ComponentSelector m_dbSelector;
protected DataSourceComponent m_dataSource;
/**
* 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 );
}
/*---------------------------------------------------------------
* 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/src/scratchpad/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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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/src/scratchpad/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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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/src/scratchpad/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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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.
*
* @returns the next Id.
*/
BigDecimal getNextBigDecimalId()
throws IdException;
/**
* Returns the next Id from the pool.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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.
*
* @returns 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/src/scratchpad/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>
* <id-generators>
* <sequence name="user-ids" logger="cm.ids">
* <dbpool>user-db</dbpool>
* <query>SELECT NEXTVAL('category_ids')</query>
* </sequence>
* </id-generators>
* </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>
* <role
name="org.apache.avalon.excalibur.datasource.ids.IdGeneratorSelector"
* shorthand="id-generators"
*
default-class="org.apache.avalon.excalibur.component.ExcaliburComponentSelector">
* <hint shorthand="sequence"
*
class="org.apache.avalon.excalibur.datasource.ids.SequenceIdGenerator"/>
* </role>
* </pre>
*
* To configure your component to use the IdGenerator declared above, its
configuration should look
* something like the following:
* <pre>
* <user-service logger="cm">
* <dbpool>user-db</dbpool>
* <id-generator>user-ids</id-generator>
* </user-service>
* </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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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.
*
* @returns 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.
*
* @returns 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/src/scratchpad/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.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
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>
* <id-generators>
* <table name="user-ids" big-decimals="true" block-size="1"
table="ids"
* key-table="event-type" logger="cm.ids">
* <dbpool>user-db</dbpool>
* </table>
* </id-generators>
* </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>
* <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>
* </pre>
*
* To configure your component to use the IdGenerator declared above, its
configuration should look
* something like the following:
* <pre>
* <user-service logger="cm">
* <dbpool>user-db</dbpool>
* <id-generator>user-ids</id-generator>
* </user-service>
* </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:[EMAIL PROTECTED]">Leif Mortenson</a>
* @version CVS $Revision: 1.1 $ $Date: 2002/02/08 12:27:32 $
* @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.
*
* @returns 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." );
}
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 );
}
}
/*---------------------------------------------------------------
* AbstractDataSourceBlockIdGenerator Methods
*-------------------------------------------------------------*/
/**
* Allocates a block, of the given size, of ids from the database.
*
* @param blockSize number of Ids which are to be allocated.
*
* @returns 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
{
return (BigDecimal)allocateIdBlock( blockSize, true );
}
/**
* Allocates a block, of the given size, of ids from the database.
*
* @param blockSize number of Ids which are to be allocated.
*
* @returns 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
{
return ((Long)allocateIdBlock( blockSize, false )).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/src/scratchpad/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:[EMAIL PROTECTED]">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-size-does-not-exist" );
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-size-1" );
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-size-10" );
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-size-100" );
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" );
IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select(
"ids-size-10-bd" );
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 );
}
}
public void testMaxByteIds() throws Exception
{
getLogger().info( "testMaxByteIds" );
IdGenerator idGenerator = (IdGenerator)m_idGeneratorSelector.select(
"ids-size-10" );
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-size-10" );
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-size-10" );
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-size-10" );
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
*-------------------------------------------------------------*/
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( "next_id 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( "next_id 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/src/scratchpad/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="DEBUG">
<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-size-does-not-exist" block-size="1" table="ids"
key-table="does-not-exist" logger="id-gen">
<dbpool>test-db</dbpool>
</table>
<table name="ids-size-1" block-size="1" table="ids"
key-table="test" logger="id-gen">
<dbpool>test-db</dbpool>
</table>
<table name="ids-size-10" block-size="10" table="ids"
key-table="test" logger="id-gen">
<dbpool>test-db</dbpool>
</table>
<table name="ids-size-100" block-size="100" table="ids"
key-table="test"
logger="id-gen">
<dbpool>test-db</dbpool>
</table>
<table name="ids-size-10-bd" big-decimals="true" block-size="10"
table="ids"
key-table="test" logger="id-gen">
<dbpool>test-db</dbpool>
</table>
</id-generators>
</components>
</testcase>
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>