/*
* Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com>
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: DbConnection.java 3918 2008-04-14 17:35:35Z gbevin $
*/
package com.uwyn.rife.database;
import com.uwyn.rife.database.exceptions.*;
import com.uwyn.rife.database.queries.Query;
import com.uwyn.rife.tools.ExceptionUtils;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.logging.Logger;
/**
* Represents one connection to a database. A connection has to be obtained by
* using the <code>getConnection</code> method on a <code>Datasource</code>
* instance. The resulting <code>DbConnection</code> instance can be used to
* obtain statement objects from and to manage transactions.
* <p>Statements are used to execute SQL queries either in a static or in a
* prepared fashion. This corresponds to the <code>DbStatement</code> and
* <code>DbPreparedStatement</code> classes. Look there for details about how
* to use them. A <code>DbConnection</code> keeps track of which statements
* have been openened and will automatically close them when database access
* errors occur or when the connection itself is closed.
* <p>Several statements can be executed as a whole through the use of
* transactions. Only if they all succeeded, the transaction should be
* committed and all the modifications will be preserved. Otherwise, the
* transaction should be rolled back, and the modifications will not be
* integrated into the general data storage. When a transaction has been
* started through the <code>beginTransaction()</code> method, it will be
* bound to the currently executing thread. Other threads will not be able to
* manipulate the transaction status and if they obtain and execute
* statements, they will be put in a wait state and woken up again after the
* transaction has finished.
*
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @version $Revision: 3918 $
* @see com.uwyn.rife.database.Datasource#getConnection()
* @see com.uwyn.rife.database.DbStatement
* @see com.uwyn.rife.database.DbPreparedStatement
* @since 1.0
*/
public class DbConnection
{
private static final int TRANSACTIONS_SUPPORT_UNKNOWN = -1;
private static final int TRANSACTIONS_UNSUPPORTED = 0;
private static final int TRANSACTIONS_SUPPORTED = 1;
private final Datasource mDatasource;
private Connection mConnection = null;
private ArrayList<DbStatement> mStatements = null;
private int mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
private Thread mTransactionThread = null;
/**
* Creates a new <code>DbConnection</code> instance and binds it to a
* <code>Datasource</code> and a regular JDBC <code>Connection</code>.
*
* @param connection the JDBC <code>Connection</code> that will be used
* @param datasource the <code>Datasource</code> this connection been
* obtained from
* @since 1.0
*/
DbConnection(Connection connection, Datasource datasource)
{
assert connection != null;
assert datasource != null;
mDatasource = datasource;
mConnection = connection;
mStatements = new ArrayList<DbStatement>();
assert mConnection != null;
assert mDatasource != null;
assert mStatements != null;
assert 0 == mStatements.size();
}
/**
* Retrieves the datasource this connection has been obtained from.
*
* @return the <code>Datasource</code> instance this connection has been
* obtained from
* @since 1.0
*/
public Datasource getDatasource()
{
return mDatasource;
}
/**
* Releases all the resources that are being used by this connection. If
* the connection has been obtained from a pooled datasource, it will not
* be closed, but reused later. If the datasource isn't pooled, the
* connection itself will be closed as expected.
* <p>Any ongoing transactions will be automatically rolled-back.
* <p>All open statements will be closed and if a transaction is active,
* it will be automatically rolled back and unregistered.
*
* @exception DatabaseException if a database access error occurs, or if
* an error occurred during the closing of an ongoing transaction, or if
* an error occurred during the closing of the opened statements, or if an
* error occurred during the closing of the underlying JDBC connection.
* @since 1.0
*/
public void close()
throws DatabaseException
{
if (isClosed())
{
synchronized (this)
{
this.notifyAll();
}
return;
}
synchronized (this)
{
if (hasTransactionThread() &&
!isTransactionValidForThread())
{
return;
}
try
{
// only close the connection when no pool is active and not
// inside a transaction
if (!mDatasource.isPooled() &&
!(hasTransactionThread() && isTransactionValidForThread()))
{
try
{
try
{
while (mStatements.size() > 0)
{
mStatements.get(0).close();
}
mStatements = new ArrayList<DbStatement>();
}
finally
{
try
{
mConnection.close();
}
catch (SQLException e)
{
throw new ConnectionCloseErrorException(mDatasource, e);
}
}
}
finally
{
mConnection = null;
mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
}
}
}
finally
{
this.notifyAll();
}
}
}
/**
* Indicates whether this <code>DbConnection</code> instance has been
* cleaned up or not.
*
* @return <code>true</code> if it has been cleaned up; or
* <p><code>false</code> otherwise.
*/
boolean isCleanedUp()
{
return null == mConnection;
}
/**
* This method is used to check if this <code>DbConnection</code> instance
* has been cleaned up before using the underlying JDBC connection.
*
* @exception SQLException when it has been cleaned up.
*/
void detectCleanup()
throws SQLException
{
if (isCleanedUp())
{
throw new SQLException("The connection is closed.");
}
}
/**
* Cleans up all the resources that are used by this
* <code>DbConnection</code> instance. This is mainly used to correctly
* clean up in case of errors during execution.
*
* @exception DatabaseException if an error occurred during the closing of
* an ongoing transaction, or if an error occurred during the closing of
* the opened statements,
* @since 1.0
*/
void cleanup()
throws DatabaseException
{
Thread transaction_thread = null;
// unregister a transaction thread
if (mTransactionThread != null)
{
transaction_thread = mTransactionThread;
mTransactionThread = null;
}
try
{
DbStatement statement = null;
// close all active statements
synchronized (this)
{
while (mStatements.size() > 0)
{
statement = mStatements.get(0);
statement.close();
try
{
statement.cancel();
}
catch (DatabaseException e)
{
// don't do anything since some DBs don't correct support statement cancelling
}
}
mStatements = new ArrayList<DbStatement>();
}
// reset the connect state
if (mConnection != null)
{
// rollback an ongoing transaction
try
{
mConnection.rollback();
mConnection.setAutoCommit(true);
}
catch (SQLException e)
{
}
// close the JDBC connection
try
{
mConnection.close();
}
catch (SQLException e)
{
}
}
mConnection = null;
mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
}
finally
{
if (transaction_thread != null)
{
mDatasource.getPool().unregisterThreadConnection(transaction_thread);
}
}
}
/**
* Performs the cleanup logic in case an exception is thrown during
* execution. If the connection is part of a pooled datasource, all
* connections in the pool will be closed and the whole pool will be setup
* cleanly again. If the connection isn't pooled, it will be cleaned up
* properly.
*
* @exception DatabaseException when an error occurs during the cleanup of
* the connection, or when an error occurs when the pool is set up again.
*/
void handleException()
throws DatabaseException
{
if (!mDatasource.isPooled())
{
synchronized (mDatasource)
{
synchronized (this)
{
// cleanup the connection resources
cleanup();
this.notifyAll();
}
}
}
else
{
// recreate all the pooled connections
mDatasource.getPool().recreateConnection(this);
}
}
/**
* Creates a new <code>DbStatement</code> instance for this connection. It
* will be registered and automatically closed when this
* <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return a new <code>DbStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbStatement</code> instance
* @see com.uwyn.rife.database.DbStatement
* @see #getPreparedStatement(String)
* @see #getPreparedStatement(Query)
* @since 1.0
*/
public DbStatement createStatement()
throws DatabaseException
{
try
{
detectCleanup();
Statement statement = mConnection.createStatement();
synchronized (this)
{
DbStatement db_statement = new DbStatement(this, statement);
mStatements.add(db_statement);
return db_statement;
}
}
catch (SQLException e)
{
handleException();
throw new StatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbStatement</code> instance for this connection with
* the given type and concurrency. It
* will be registered and automatically closed when this
* <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param resultSetType a result set type; one of ResultSet.TYPE_FORWARD_ONLY,
* ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
* @param resultSetConcurrency a concurrency type; one of
* ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
* @return a new <code>DbStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbStatement</code> instance
* @see com.uwyn.rife.database.DbStatement
* @see #getPreparedStatement(String)
* @see #getPreparedStatement(Query)
* @since 1.0
*/
public DbStatement createStatement(int resultSetType, int resultSetConcurrency)
throws DatabaseException
{
try
{
detectCleanup();
Statement statement = mConnection.createStatement(resultSetType, resultSetConcurrency);
synchronized (this)
{
DbStatement db_statement = new DbStatement(this, statement);
mStatements.add(db_statement);
return db_statement;
}
}
catch (SQLException e)
{
handleException();
throw new StatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbStatement</code> instance for this connection with
* the given type, concurrency, and holdability.. It
* will be registered and automatically closed when this
* <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param resultSetType a result set type; one of ResultSet.TYPE_FORWARD_ONLY,
* ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
* @param resultSetConcurrency a concurrency type; one of
* ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
* @param resultSetHoldability one of the following ResultSet constants:
* ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT
* @return a new <code>DbStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbStatement</code> instance
* @see com.uwyn.rife.database.DbStatement
* @see #getPreparedStatement(String)
* @see #getPreparedStatement(Query)
* @since 1.0
*/
public DbStatement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws DatabaseException
{
try
{
detectCleanup();
Statement statement = mConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);
synchronized (this)
{
DbStatement db_statement = new DbStatement(this, statement);
mStatements.add(db_statement);
return db_statement;
}
}
catch (SQLException e)
{
handleException();
throw new StatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbPreparedStatement</code> instance for this
* connection from a regular SQL query string. Since the statement is
* created from a <code>String</code> and not a
* <code>ParametrizedQuery</code> instance, information is lacking to be
* able to fully use the features of the resulting
* <code>DbPreparedStatement</code> instance. It's recommended to use the
* {@link #getPreparedStatement(Query)} method instead if this is
* possible.
* <p>The new statement will be registered and automatically closed when
* this <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param sql a <code>String</code> instance with the SQL that is used to
* set up the prepared statement
* @return a new <code>DbPreparedStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbPreparedStatement</code> instance
* @see com.uwyn.rife.database.DbPreparedStatement
* @see #createStatement()
* @see #getPreparedStatement(Query)
* @since 1.0
*/
public DbPreparedStatement getPreparedStatement(String sql)
throws DatabaseException
{
if (null == sql) throw new IllegalArgumentException("sql can't be null.");
if (0 == sql.length()) throw new IllegalArgumentException("sql can't be empty.");
try
{
detectCleanup();
PreparedStatement prepared_statement = mConnection.prepareStatement(sql);
synchronized (this)
{
DbPreparedStatement db_prepared_statement = new DbPreparedStatement(this, sql, prepared_statement);
mStatements.add(db_prepared_statement);
return db_prepared_statement;
}
}
catch (SQLException e)
{
handleException();
throw new PreparedStatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbPreparedStatement</code> instance for this
* connection from a <code>Query</code> instance that has the capability
* to retrieve auto-generated keys. The given constant tells the driver
* whether it should make auto-generated keys available for retrieval.
* This parameter is ignored if the SQL statement is not an INSERT
* statement.
* <p>Since the statement is created from a <code>String</code> and not a
* <code>ParametrizedQuery</code> instance, information is lacking to be
* able to fully use the features of the resulting
* <code>DbPreparedStatement</code> instance. It's recommended to use the
* {@link #getPreparedStatement(Query)} method instead if this is
* possible.
* <p>The new statement will be registered and automatically closed when
* this <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param sql a <code>String</code> instance with the SQL that is used to
* set up the prepared statement
* @param autoGeneratedKeys a flag indicating whether auto-generated keys
* should be returned; one of <code>Statement.RETURN_GENERATED_KEYS</code>
* or <code>Statement.NO_GENERATED_KEYS</code>
* @return a new <code>DbPreparedStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbPreparedStatement</code> instance
* @see com.uwyn.rife.database.DbPreparedStatement
* @see #createStatement()
* @see #getPreparedStatement(Query)
* @since 1.0
*/
public DbPreparedStatement getPreparedStatement(String sql, int autoGeneratedKeys)
throws DatabaseException
{
if (null == sql) throw new IllegalArgumentException("sql can't be null.");
if (0 == sql.length()) throw new IllegalArgumentException("sql can't be empty.");
try
{
detectCleanup();
PreparedStatement prepared_statement = mConnection.prepareStatement(sql, autoGeneratedKeys);
synchronized (this)
{
DbPreparedStatement db_prepared_statement = new DbPreparedStatement(this, sql, prepared_statement);
mStatements.add(db_prepared_statement);
return db_prepared_statement;
}
}
catch (SQLException e)
{
handleException();
throw new PreparedStatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbPreparedStatement</code> instance for this
* connection from a <code>Query</code> instance. Thanks to the additional
* meta-data that's stored in a <code>Query</code> object, it's possible
* to use the additional features that the
* <code>DbPreparedStatement</code> provides on top of regular JDBC
* methods.
* <p>The new statement will be registered and automatically closed when
* this <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param query a <code>Query</code> instance that is used to set up the
* prepared statement
* @return a new <code>DbPreparedStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbPreparedStatement</code> instance
* @see com.uwyn.rife.database.DbPreparedStatement
* @see #createStatement()
* @see #getPreparedStatement(String)
* @since 1.0
*/
public DbPreparedStatement getPreparedStatement(Query query)
throws DatabaseException
{
if (null == query) throw new IllegalArgumentException("query can't be null.");
if (null == query.getSql()) throw new IllegalArgumentException("query can't be empty.");
try
{
detectCleanup();
String sql = query.getSql();
PreparedStatement prepared_statement = mConnection.prepareStatement(sql);
synchronized (this)
{
DbPreparedStatement db_prepared_statement = new DbPreparedStatement(this, query, prepared_statement);
mStatements.add(db_prepared_statement);
return db_prepared_statement;
}
}
catch (SQLException e)
{
handleException();
throw new PreparedStatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbPreparedStatement</code> instance for this
* connection from a <code>Query</code> instance that has the capability
* to retrieve auto-generated keys. The given constant tells the driver
* whether it should make auto-generated keys available for retrieval.
* This parameter is ignored if the SQL statement is not an INSERT
* statement.
* <p>Thanks to the additional meta-data that's stored in a
* <code>Query</code> object, it's possible to use the additional features
* that the <code>DbPreparedStatement</code> provides on top of regular
* JDBC methods.
* <p>The new statement will be registered and automatically closed when
* this <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param query a <code>Query</code> instance that is used to set up the
* prepared statement
* @param autoGeneratedKeys a flag indicating whether auto-generated keys
* should be returned; one of <code>Statement.RETURN_GENERATED_KEYS</code>
* or <code>Statement.NO_GENERATED_KEYS</code>
* @return a new <code>DbPreparedStatement</code> instance
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbPreparedStatement</code> instance
* @see com.uwyn.rife.database.DbPreparedStatement
* @see #createStatement()
* @see #getPreparedStatement(String)
* @since 1.0
*/
public DbPreparedStatement getPreparedStatement(Query query, int autoGeneratedKeys)
throws DatabaseException
{
if (null == query) throw new IllegalArgumentException("query can't be null.");
if (null == query.getSql()) throw new IllegalArgumentException("query can't be empty.");
try
{
detectCleanup();
String sql = query.getSql();
PreparedStatement prepared_statement = mConnection.prepareStatement(sql, autoGeneratedKeys);
synchronized (this)
{
DbPreparedStatement db_prepared_statement = new DbPreparedStatement(this, query, prepared_statement);
mStatements.add(db_prepared_statement);
return db_prepared_statement;
}
}
catch (SQLException e)
{
handleException();
throw new PreparedStatementCreationErrorException(mDatasource, e);
}
}
/**
* Creates a new <code>DbPreparedStatement</code> instance for this
* connection from a <code>Query</code> instance that will generate
* <code>ResultSet</code> objects with the given type, concurrency,
* and holdability.
* <p>
* This method is the same as the <code>getPreparedStatement</code> method
* above, but it allows the default result set
* type, concurrency, and holdability to be overridden.
* <p>Thanks to the additional meta-data that's stored in a
* <code>Query</code> object, it's possible to use the additional features
* that the <code>DbPreparedStatement</code> provides on top of regular
* JDBC methods.
* <p>The new statement will be registered and automatically closed when
* this <code>DbConnection</code> cleans up. It is recommended though to
* manually close the statement when it's not needed anymore for sensible
* resource preservation.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @param query a <code>Query</code> instance that is used to set up the
* prepared statement
* @param resultSetType one of the following <code>ResultSet</code>
* constants:
* <code>ResultSet.TYPE_FORWARD_ONLY</code>,
* <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
* <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
* @param resultSetConcurrency one of the following <code>ResultSet</code>
* constants:
* <code>ResultSet.CONCUR_READ_ONLY</code> or
* <code>ResultSet.CONCUR_UPDATABLE</code>
* @param resultSetHoldability one of the following <code>ResultSet</code>
* constants:
* <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
* <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
* @return a new <code>DbPreparedStatement</code> instance, that will generate
* <code>ResultSet</code> objects with the given type,
* concurrency, and holdability
* @exception DatabaseException when an exception has occurred during the
* creation of the <code>DbPreparedStatement</code> instance
* @see com.uwyn.rife.database.DbPreparedStatement
* @see #createStatement()
* @see #getPreparedStatement(String)
* @since 1.2
*/
public DbPreparedStatement getPreparedStatement(Query query, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws DatabaseException
{
if (null == query) throw new IllegalArgumentException("query can't be null.");
if (null == query.getSql()) throw new IllegalArgumentException("query can't be empty.");
try
{
detectCleanup();
String sql = query.getSql();
PreparedStatement prepared_statement = mConnection.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability);
synchronized (this)
{
DbPreparedStatement db_prepared_statement = new DbPreparedStatement(this, query, prepared_statement);
mStatements.add(db_prepared_statement);
return db_prepared_statement;
}
}
catch (SQLException e)
{
handleException();
throw new PreparedStatementCreationErrorException(mDatasource, e);
}
}
/**
* Removes a <code>DbStatement</code> instance from the collection of
* managed statements. If the statement is not present, no error or
* exception is thrown.
*
* @param statement the <code>DbStatement</code> that has to be removed.
* @since 1.0
*/
void releaseStatement(DbStatement statement)
{
synchronized (this)
{
mStatements.remove(statement);
}
}
/**
* Indicates whether the <code>Datasource</code> of this
* <code>DbConnection</code> supports transactions or not.
* <p>This information is only retrieved once and cached for the rest of
* the lifetime of this connection.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return <code>true</code> if the <code>Datasource</code> supports
* transactions; or
* <p><code>false</code> if the <code>Datasource</code> doesn't support
* transactions.
* @exception DatabaseException when an error occurred during the
* verification of the transaction support
* @see #beginTransaction()
* @see #commit()
* @see #rollback()
* @see #isFree()
* @see #isTransactionValidForThread()
* @since 1.0
*/
public boolean supportsTransactions()
throws DatabaseException
{
try
{
synchronized (this)
{
detectCleanup();
if (TRANSACTIONS_SUPPORT_UNKNOWN == mSupportsTransactions)
{
if (mConnection.getMetaData().supportsTransactions())
{
mSupportsTransactions = TRANSACTIONS_SUPPORTED;
}
else
{
mSupportsTransactions = TRANSACTIONS_UNSUPPORTED;
}
}
return TRANSACTIONS_SUPPORTED == mSupportsTransactions;
}
}
catch (SQLException e)
{
handleException();
throw new TransactionSupportCheckErrorException(mDatasource, e);
}
}
/**
* <p><strong>Warning:</strong> only use the raw transaction methods if
* you really know what you're doing. It's almost always better to use the
* {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
* method of the <code>DbQueryManager</code> class instead.
* <p>Starts a new transaction if the <code>Datasource</code> supports it.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return <code>true</code> if the transaction was successfully started;
* or
* <p><code>false</code> if the <code>Datasource</code> doesn't support
* transactions, or if a transaction is already active on this
* <code>DbConnection</code>.
* @exception DatabaseException when an error occurred during the creation
* of the new transaction, or when the active transaction has timed-out.
* @see DbQueryManager#inTransaction(DbTransactionUser)
* @see #supportsTransactions()
* @see #commit()
* @see #rollback()
* @see #isFree()
* @see #isTransactionValidForThread()
* @since 1.0
*/
public boolean beginTransaction()
throws DatabaseException
{
// if the datasource doesn't support transactions,
// don't start any
if (!supportsTransactions())
{
return false;
}
synchronized (this)
{
// check if a thread has already got a hold of this connection
if (hasTransactionThread())
{
// if it's still active, it's impossible to begin a new
// transaction
return false;
}
// setup the new transaction
mTransactionThread = Thread.currentThread();
}
try
{
detectCleanup();
mConnection.setAutoCommit(false);
}
catch (SQLException e)
{
if (mTransactionThread != null)
{
mTransactionThread = null;
}
handleException();
throw new TransactionBeginErrorException(mDatasource, e);
}
mDatasource.getPool().registerThreadConnection(mTransactionThread, this);
return true;
}
/**
* <p><strong>Warning:</strong> only use the raw transaction methods if
* you really know what you're doing. It's almost always better to use the
* {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
* method of the <code>DbQueryManager</code> class instead.
* <p>Commits an active transaction.
* <p>All transaction-related resources are cleared and all the threads
* that are waiting for the transaction to terminate are woken up.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return <code>true</code> if the transaction was successfully
* committed; or
* <p><code>false</code> if the <code>Datasource</code> doesn't support
* transactions, or when no transaction is active on this
* <code>DbConnection</code>, or when the executing thread isn't the
* thread that began the transaction.
* @exception DatabaseException when an error occurred during the commit
* of the active transaction, or when the active transaction has
* timed-out.
* @see DbQueryManager#inTransaction(DbTransactionUser)
* @see #supportsTransactions()
* @see #beginTransaction()
* @see #rollback()
* @see #isFree()
* @see #isTransactionValidForThread()
* @since 1.0
*/
public boolean commit()
throws DatabaseException
{
// if the datasource doesn't support transactions,
// it's impossible to commit one
if (!supportsTransactions())
{
return false;
}
synchronized (this)
{
// if the transaction isn't valid for the current thread
// refuse to commit it
if (!isTransactionValidForThread())
{
return false;
}
}
try
{
detectCleanup();
mConnection.commit();
mConnection.setAutoCommit(true);
}
catch (SQLException e)
{
handleException();
throw new TransactionCommitErrorException(mDatasource, e);
}
finally
{
synchronized (this)
{
if (mTransactionThread != null)
{
mTransactionThread = null;
}
this.notifyAll();
}
mDatasource.getPool().unregisterThreadConnection(Thread.currentThread());
}
return true;
}
/**
* <p><strong>Warning:</strong> only use the raw transaction methods if
* you really know what you're doing. It's almost always better to use the
* {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
* method of the <code>DbQueryManager</code> class instead.
* <p>Rolls-back an active transaction.
* <p>All transaction-related resources are cleared and all the threads
* that are waiting for the transaction to terminate are woken up.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return <code>true</code> if the transaction was successfully
* rolled-back; or
* <p><code>false</code> if the <code>Datasource</code> doesn't support
* transactions, or when no transaction is active on this
* <code>DbConnection</code>, or when the executing thread isn't the
* thread that began the transaction.
* @exception DatabaseException when an error occurred during the rollback
* of the active transaction, or when the active transaction has
* timed-out.
* @see DbQueryManager#inTransaction(DbTransactionUser)
* @see #supportsTransactions()
* @see #beginTransaction()
* @see #commit()
* @see #isFree()
* @see #isTransactionValidForThread()
* @since 1.0
*/
public boolean rollback()
throws DatabaseException
{
// if the datasource doesn't support transactions,
// it's impossible to roll one back
if (!supportsTransactions())
{
return false;
}
synchronized (this)
{
// if the transaction isn't valid for the current thread
// refuse to roll it back
if (!isTransactionValidForThread())
{
return false;
}
}
try
{
detectCleanup();
mConnection.rollback();
mConnection.setAutoCommit(true);
}
catch (SQLException e)
{
handleException();
throw new TransactionRollbackErrorException(mDatasource, e);
}
finally
{
synchronized (this)
{
if (mTransactionThread != null)
{
mTransactionThread = null;
}
this.notifyAll();
}
mDatasource.getPool().unregisterThreadConnection(Thread.currentThread());
}
return true;
}
/**
* Indicates whether this <code>DbConnection</code> is free to execute
* statements for the current thread.
*
* @return <code>true</code> if a statement can be executed by the current
* thread on this <code>DbConnection</code>; or
* <p><code>false</code> if the connection is closed or when a transaction
* is already active on this <code>DbConnection</code> for another thread.
* @see #supportsTransactions()
* @see #beginTransaction()
* @see #commit()
* @see #rollback()
* @see #isTransactionValidForThread()
* @since 1.0
*/
public boolean isFree()
{
synchronized (this)
{
if (isCleanedUp())
{
return false;
}
if (!hasTransactionThread())
{
return true;
}
if (isTransactionValidForThread())
{
return true;
}
return false;
}
}
/**
* Indicates whether this connections has an active transaction thread.
*
* @return <code>true</code> is an active transaction thread is present;
* or
* <p><code>false</code> otherwise.
*/
private boolean hasTransactionThread()
{
return mTransactionThread != null;
}
/**
* Indicates whether the current thread has a valid transaction going on
* for the execution of statements.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again.
*
* @return <code>true</code> if a transaction is active that can be used
* by the current thread; or
* <p><code>false</code> if the connection is closed, doesn't support
* transactions, has no active transaction or has a transaction that was
* started by another thread.
* @exception DatabaseException when errors occurred during the
* verification of the connection's open status and support for
* transactions
* @see #supportsTransactions()
* @see #beginTransaction()
* @see #commit()
* @see #rollback()
* @see #isFree()
* @since 1.0
*/
public boolean isTransactionValidForThread()
{
synchronized (this)
{
if (!hasTransactionThread())
{
return false;
}
return mTransactionThread == Thread.currentThread();
}
}
/**
* Indicates whether this <code>DbConnection</code>'s connection to the
* database is closed.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically cleaned up. Also, any ongoing transaction will be
* rolled-back automatically.
*
* @return <code>true</code> when this <code>DbConnection</code> is
* closed; or
* <p><code>false</code> if it's connected.
* @exception DatabaseException when an error occurred during the
* verification of the JDBC connection's closed status
* @since 1.0
*/
public boolean isClosed()
throws DatabaseException
{
try
{
if (null == mConnection)
{
return true;
}
return mConnection.isClosed();
}
catch (SQLException e)
{
cleanup();
throw new ConnectionStatusErrorException(mDatasource, e);
}
}
/**
* Retrieves a <code>DatabaseMetaData</code> object that contains metadata
* about the database to which this <code>DbConnection</code> object
* represents a connection. The metadata includes information about the
* database's tables, its supported SQL grammar, its stored procedures,
* the capabilities of this connection, and so on.
* <p>If an exception is thrown, this <code>DbConnection</code> is
* automatically closed and if it's part of a pool, all the other
* connections are closed too and the pool is set up again. Also, any
* ongoing transaction will be rolled-back automatically.
*
* @return a <code>DatabaseMetaData</code> object for this
* <code>DbConnection</code> instance; or
* <p><code>null</code> if the <code>DbConnection</code> instance is not
* connected.
* @exception DatabaseException if a database access error occurs
*/
public DatabaseMetaData getMetaData()
throws DatabaseException
{
try
{
detectCleanup();
return mConnection.getMetaData();
}
catch (SQLException e)
{
handleException();
throw new ConnectionMetaDataErrorException(mDatasource, e);
}
}
/**
* Attempts to change the transaction isolation level for this
* <code>DbConnection</code> object to the one given. The constants
* defined in the interface <code>Connection</code> are the possible
* transaction isolation levels.
*
* @param level transaction isolation level constant defined in the <code>{@link
* java.sql.Connection Connection}</code> interface
* @exception DatabaseException if a database access error occurs
* @see java.sql.Connection
*/
public void setTransactionIsolation(int level)
throws DatabaseException
{
try
{
detectCleanup();
mConnection.setTransactionIsolation(level);
}
catch (SQLException e)
{
handleException();
throw new ConnectionMetaDataErrorException(mDatasource, e);
}
}
/**
* Ensures that this <code>DbConnection</code> is correctly cleaned-up
* when it's garbage collected.
*
* @exception Throwable if an error occurred during the finalization
* @since 1.0
*/
protected void finalize()
throws Throwable
{
synchronized (this)
{
cleanup();
this.notifyAll();
}
super.finalize();
}
/**
* Simply clones the instance with the default clone method. This creates
* a shallow copy of all fields and the clone will in fact just be another
* reference to the same underlying data. The independence of each cloned
* instance is consciously not respected since they rely on resources that
* can't be cloned.
*
* @since 1.0
*/
public Object clone()
{
try
{
return super.clone();
}
catch (CloneNotSupportedException e)
{
// this should never happen
Logger.getLogger("com.uwyn.rife.database").severe(ExceptionUtils.getExceptionStackTrace(e));
return null;
}
}
}