0001: /*
0002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
0003: * Distributed under the terms of either:
0004: * - the common development and distribution license (CDDL), v1.0; or
0005: * - the GNU Lesser General Public License, v2.1 or later
0006: * $Id: DbConnection.java 3634 2007-01-08 21:42:24Z gbevin $
0007: */
0008: package com.uwyn.rife.database;
0009:
0010: import com.uwyn.rife.database.exceptions.*;
0011:
0012: import com.uwyn.rife.database.queries.Query;
0013: import com.uwyn.rife.tools.ExceptionUtils;
0014: import java.sql.Connection;
0015: import java.sql.DatabaseMetaData;
0016: import java.sql.PreparedStatement;
0017: import java.sql.SQLException;
0018: import java.sql.Statement;
0019: import java.util.ArrayList;
0020: import java.util.logging.Logger;
0021:
0022: /**
0023: * Represents one connection to a database. A connection has to be obtained by
0024: * using the <code>getConnection</code> method on a <code>Datasource</code>
0025: * instance. The resulting <code>DbConnection</code> instance can be used to
0026: * obtain statement objects from and to manage transactions.
0027: * <p>Statements are used to execute SQL queries either in a static or in a
0028: * prepared fashion. This corresponds to the <code>DbStatement</code> and
0029: * <code>DbPreparedStatement</code> classes. Look there for details about how
0030: * to use them. A <code>DbConnection</code> keeps track of which statements
0031: * have been openened and will automatically close them when database access
0032: * errors occur or when the connection itself is closed.
0033: * <p>Several statements can be executed as a whole through the use of
0034: * transactions. Only if they all succeeded, the transaction should be
0035: * committed and all the modifications will be preserved. Otherwise, the
0036: * transaction should be rolled back, and the modifications will not be
0037: * integrated into the general data storage. When a transaction has been
0038: * started through the <code>beginTransaction()</code> method, it will be
0039: * bound to the currently executing thread. Other threads will not be able to
0040: * manipulate the transaction status and if they obtain and execute
0041: * statements, they will be put in a wait state and woken up again after the
0042: * transaction has finished.
0043: *
0044: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
0045: * @version $Revision: 3634 $
0046: * @see com.uwyn.rife.database.Datasource#getConnection()
0047: * @see com.uwyn.rife.database.DbStatement
0048: * @see com.uwyn.rife.database.DbPreparedStatement
0049: * @since 1.0
0050: */
0051: public class DbConnection {
0052: private static final int TRANSACTIONS_SUPPORT_UNKNOWN = -1;
0053: private static final int TRANSACTIONS_UNSUPPORTED = 0;
0054: private static final int TRANSACTIONS_SUPPORTED = 1;
0055:
0056: private final Datasource mDatasource;
0057: private Connection mConnection = null;
0058: private ArrayList<DbStatement> mStatements = null;
0059: private int mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
0060: private Thread mTransactionThread = null;
0061:
0062: /**
0063: * Creates a new <code>DbConnection</code> instance and binds it to a
0064: * <code>Datasource</code> and a regular JDBC <code>Connection</code>.
0065: *
0066: * @param connection the JDBC <code>Connection</code> that will be used
0067: * @param datasource the <code>Datasource</code> this connection been
0068: * obtained from
0069: * @since 1.0
0070: */
0071: DbConnection(Connection connection, Datasource datasource) {
0072: assert connection != null;
0073: assert datasource != null;
0074:
0075: mDatasource = datasource;
0076: mConnection = connection;
0077: mStatements = new ArrayList<DbStatement>();
0078:
0079: assert mConnection != null;
0080: assert mDatasource != null;
0081: assert mStatements != null;
0082: assert 0 == mStatements.size();
0083: }
0084:
0085: /**
0086: * Retrieves the datasource this connection has been obtained from.
0087: *
0088: * @return the <code>Datasource</code> instance this connection has been
0089: * obtained from
0090: * @since 1.0
0091: */
0092: public Datasource getDatasource() {
0093: return mDatasource;
0094: }
0095:
0096: /**
0097: * Releases all the resources that are being used by this connection. If
0098: * the connection has been obtained from a pooled datasource, it will not
0099: * be closed, but reused later. If the datasource isn't pooled, the
0100: * connection itself will be closed as expected.
0101: * <p>Any ongoing transactions will be automatically rolled-back.
0102: * <p>All open statements will be closed and if a transaction is active,
0103: * it will be automatically rolled back and unregistered.
0104: *
0105: * @exception DatabaseException if a database access error occurs, or if
0106: * an error occurred during the closing of an ongoing transaction, or if
0107: * an error occurred during the closing of the opened statements, or if an
0108: * error occurred during the closing of the underlying JDBC connection.
0109: * @since 1.0
0110: */
0111: public void close() throws DatabaseException {
0112: if (isClosed()) {
0113: synchronized (this ) {
0114: this .notifyAll();
0115: }
0116: return;
0117: }
0118:
0119: synchronized (this ) {
0120: if (hasTransactionThread()
0121: && !isTransactionValidForThread()) {
0122: return;
0123: }
0124:
0125: try {
0126: // only close the connection when no pool is active and not
0127: // inside a transaction
0128: if (!mDatasource.isPooled()
0129: && !(hasTransactionThread() && isTransactionValidForThread())) {
0130: try {
0131: try {
0132: while (mStatements.size() > 0) {
0133: mStatements.get(0).close();
0134: }
0135: mStatements = new ArrayList<DbStatement>();
0136: } finally {
0137: try {
0138: mConnection.close();
0139: } catch (SQLException e) {
0140: throw new ConnectionCloseErrorException(
0141: mDatasource, e);
0142: }
0143: }
0144: } finally {
0145: mConnection = null;
0146: mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
0147: }
0148: }
0149: } finally {
0150: this .notifyAll();
0151: }
0152: }
0153: }
0154:
0155: /**
0156: * Indicates whether this <code>DbConnection</code> instance has been
0157: * cleaned up or not.
0158: *
0159: * @return <code>true</code> if it has been cleaned up; or
0160: * <p><code>false</code> otherwise.
0161: */
0162: boolean isCleanedUp() {
0163: return null == mConnection;
0164: }
0165:
0166: /**
0167: * This method is used to check if this <code>DbConnection</code> instance
0168: * has been cleaned up before using the underlying JDBC connection.
0169: *
0170: * @exception SQLException when it has been cleaned up.
0171: */
0172: void detectCleanup() throws SQLException {
0173: if (isCleanedUp()) {
0174: throw new SQLException("The connection is closed.");
0175: }
0176: }
0177:
0178: /**
0179: * Cleans up all the resources that are used by this
0180: * <code>DbConnection</code> instance. This is mainly used to correctly
0181: * clean up in case of errors during execution.
0182: *
0183: * @exception DatabaseException if an error occurred during the closing of
0184: * an ongoing transaction, or if an error occurred during the closing of
0185: * the opened statements,
0186: * @since 1.0
0187: */
0188: void cleanup() throws DatabaseException {
0189: Thread transaction_thread = null;
0190: // unregister a transaction thread
0191: if (mTransactionThread != null) {
0192: transaction_thread = mTransactionThread;
0193: mTransactionThread = null;
0194: }
0195:
0196: try {
0197: DbStatement statement = null;
0198:
0199: // close all active statements
0200: synchronized (this ) {
0201: while (mStatements.size() > 0) {
0202: statement = mStatements.get(0);
0203: statement.close();
0204: try {
0205: statement.cancel();
0206: } catch (DatabaseException e) {
0207: // don't do anything since some DBs don't correct support statement cancelling
0208: }
0209: }
0210: mStatements = new ArrayList<DbStatement>();
0211: }
0212:
0213: // reset the connect state
0214: if (mConnection != null) {
0215: // rollback an ongoing transaction
0216: try {
0217: mConnection.rollback();
0218: mConnection.setAutoCommit(true);
0219: } catch (SQLException e) {
0220: }
0221:
0222: // close the JDBC connection
0223: try {
0224: mConnection.close();
0225: } catch (SQLException e) {
0226: }
0227: }
0228: mConnection = null;
0229: mSupportsTransactions = TRANSACTIONS_SUPPORT_UNKNOWN;
0230: } finally {
0231: if (transaction_thread != null) {
0232: mDatasource.getPool().unregisterThreadConnection(
0233: transaction_thread);
0234: }
0235: }
0236: }
0237:
0238: /**
0239: * Performs the cleanup logic in case an exception is thrown during
0240: * execution. If the connection is part of a pooled datasource, all
0241: * connections in the pool will be closed and the whole pool will be setup
0242: * cleanly again. If the connection isn't pooled, it will be cleaned up
0243: * properly.
0244: *
0245: * @exception DatabaseException when an error occurs during the cleanup of
0246: * the connection, or when an error occurs when the pool is set up again.
0247: */
0248: void handleException() throws DatabaseException {
0249: if (!mDatasource.isPooled()) {
0250: synchronized (mDatasource) {
0251: synchronized (this ) {
0252: // cleanup the connection resources
0253: cleanup();
0254: this .notifyAll();
0255: }
0256: }
0257: } else {
0258: // recreate all the pooled connections
0259: mDatasource.getPool().recreateConnection(this );
0260: }
0261: }
0262:
0263: /**
0264: * Creates a new <code>DbStatement</code> instance for this connection. It
0265: * will be registered and automatically closed when this
0266: * <code>DbConnection</code> cleans up. It is recommended though to
0267: * manually close the statement when it's not needed anymore for sensible
0268: * resource preservation.
0269: * <p>If an exception is thrown, this <code>DbConnection</code> is
0270: * automatically closed and if it's part of a pool, all the other
0271: * connections are closed too and the pool is set up again. Also, any
0272: * ongoing transaction will be rolled-back automatically.
0273: *
0274: * @return a new <code>DbStatement</code> instance
0275: * @exception DatabaseException when an exception has occurred during the
0276: * creation of the <code>DbStatement</code> instance
0277: * @see com.uwyn.rife.database.DbStatement
0278: * @see #getPreparedStatement(String)
0279: * @see #getPreparedStatement(Query)
0280: * @since 1.0
0281: */
0282: public DbStatement createStatement() throws DatabaseException {
0283: try {
0284: detectCleanup();
0285:
0286: Statement statement = mConnection.createStatement();
0287:
0288: synchronized (this ) {
0289: DbStatement db_statement = new DbStatement(this ,
0290: statement);
0291: mStatements.add(db_statement);
0292:
0293: return db_statement;
0294: }
0295: } catch (SQLException e) {
0296: handleException();
0297: throw new StatementCreationErrorException(mDatasource, e);
0298: }
0299: }
0300:
0301: /**
0302: * Creates a new <code>DbStatement</code> instance for this connection with
0303: * the given type and concurrency. It
0304: * will be registered and automatically closed when this
0305: * <code>DbConnection</code> cleans up. It is recommended though to
0306: * manually close the statement when it's not needed anymore for sensible
0307: * resource preservation.
0308: * <p>If an exception is thrown, this <code>DbConnection</code> is
0309: * automatically closed and if it's part of a pool, all the other
0310: * connections are closed too and the pool is set up again. Also, any
0311: * ongoing transaction will be rolled-back automatically.
0312: *
0313: * @param resultSetType a result set type; one of ResultSet.TYPE_FORWARD_ONLY,
0314: * ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
0315: * @param resultSetConcurrency a concurrency type; one of
0316: * ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
0317: * @return a new <code>DbStatement</code> instance
0318: * @exception DatabaseException when an exception has occurred during the
0319: * creation of the <code>DbStatement</code> instance
0320: * @see com.uwyn.rife.database.DbStatement
0321: * @see #getPreparedStatement(String)
0322: * @see #getPreparedStatement(Query)
0323: * @since 1.0
0324: */
0325: public DbStatement createStatement(int resultSetType,
0326: int resultSetConcurrency) throws DatabaseException {
0327: try {
0328: detectCleanup();
0329:
0330: Statement statement = mConnection.createStatement(
0331: resultSetType, resultSetConcurrency);
0332:
0333: synchronized (this ) {
0334: DbStatement db_statement = new DbStatement(this ,
0335: statement);
0336: mStatements.add(db_statement);
0337:
0338: return db_statement;
0339: }
0340: } catch (SQLException e) {
0341: handleException();
0342: throw new StatementCreationErrorException(mDatasource, e);
0343: }
0344: }
0345:
0346: /**
0347: * Creates a new <code>DbStatement</code> instance for this connection with
0348: * the given type, concurrency, and holdability.. It
0349: * will be registered and automatically closed when this
0350: * <code>DbConnection</code> cleans up. It is recommended though to
0351: * manually close the statement when it's not needed anymore for sensible
0352: * resource preservation.
0353: * <p>If an exception is thrown, this <code>DbConnection</code> is
0354: * automatically closed and if it's part of a pool, all the other
0355: * connections are closed too and the pool is set up again. Also, any
0356: * ongoing transaction will be rolled-back automatically.
0357: *
0358: * @param resultSetType a result set type; one of ResultSet.TYPE_FORWARD_ONLY,
0359: * ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
0360: * @param resultSetConcurrency a concurrency type; one of
0361: * ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
0362: * @param resultSetHoldability one of the following ResultSet constants:
0363: * ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT
0364: * @return a new <code>DbStatement</code> instance
0365: * @exception DatabaseException when an exception has occurred during the
0366: * creation of the <code>DbStatement</code> instance
0367: * @see com.uwyn.rife.database.DbStatement
0368: * @see #getPreparedStatement(String)
0369: * @see #getPreparedStatement(Query)
0370: * @since 1.0
0371: */
0372: public DbStatement createStatement(int resultSetType,
0373: int resultSetConcurrency, int resultSetHoldability)
0374: throws DatabaseException {
0375: try {
0376: detectCleanup();
0377:
0378: Statement statement = mConnection.createStatement(
0379: resultSetType, resultSetConcurrency,
0380: resultSetHoldability);
0381:
0382: synchronized (this ) {
0383: DbStatement db_statement = new DbStatement(this ,
0384: statement);
0385: mStatements.add(db_statement);
0386:
0387: return db_statement;
0388: }
0389: } catch (SQLException e) {
0390: handleException();
0391: throw new StatementCreationErrorException(mDatasource, e);
0392: }
0393: }
0394:
0395: /**
0396: * Creates a new <code>DbPreparedStatement</code> instance for this
0397: * connection from a regular SQL query string. Since the statement is
0398: * created from a <code>String</code> and not a
0399: * <code>ParametrizedQuery</code> instance, information is lacking to be
0400: * able to fully use the features of the resulting
0401: * <code>DbPreparedStatement</code> instance. It's recommended to use the
0402: * {@link #getPreparedStatement(Query)} method instead if this is
0403: * possible.
0404: * <p>The new statement will be registered and automatically closed when
0405: * this <code>DbConnection</code> cleans up. It is recommended though to
0406: * manually close the statement when it's not needed anymore for sensible
0407: * resource preservation.
0408: * <p>If an exception is thrown, this <code>DbConnection</code> is
0409: * automatically closed and if it's part of a pool, all the other
0410: * connections are closed too and the pool is set up again. Also, any
0411: * ongoing transaction will be rolled-back automatically.
0412: *
0413: * @param sql a <code>String</code> instance with the SQL that is used to
0414: * set up the prepared statement
0415: * @return a new <code>DbPreparedStatement</code> instance
0416: * @exception DatabaseException when an exception has occurred during the
0417: * creation of the <code>DbPreparedStatement</code> instance
0418: * @see com.uwyn.rife.database.DbPreparedStatement
0419: * @see #createStatement()
0420: * @see #getPreparedStatement(Query)
0421: * @since 1.0
0422: */
0423: public DbPreparedStatement getPreparedStatement(String sql)
0424: throws DatabaseException {
0425: if (null == sql)
0426: throw new IllegalArgumentException("sql can't be null.");
0427: if (0 == sql.length())
0428: throw new IllegalArgumentException("sql can't be empty.");
0429:
0430: try {
0431: detectCleanup();
0432:
0433: PreparedStatement prepared_statement = mConnection
0434: .prepareStatement(sql);
0435:
0436: synchronized (this ) {
0437: DbPreparedStatement db_prepared_statement = new DbPreparedStatement(
0438: this , sql, prepared_statement);
0439: mStatements.add(db_prepared_statement);
0440:
0441: return db_prepared_statement;
0442: }
0443: } catch (SQLException e) {
0444: handleException();
0445: throw new PreparedStatementCreationErrorException(
0446: mDatasource, e);
0447: }
0448: }
0449:
0450: /**
0451: * Creates a new <code>DbPreparedStatement</code> instance for this
0452: * connection from a <code>Query</code> instance that has the capability
0453: * to retrieve auto-generated keys. The given constant tells the driver
0454: * whether it should make auto-generated keys available for retrieval.
0455: * This parameter is ignored if the SQL statement is not an INSERT
0456: * statement.
0457: * <p>Since the statement is created from a <code>String</code> and not a
0458: * <code>ParametrizedQuery</code> instance, information is lacking to be
0459: * able to fully use the features of the resulting
0460: * <code>DbPreparedStatement</code> instance. It's recommended to use the
0461: * {@link #getPreparedStatement(Query)} method instead if this is
0462: * possible.
0463: * <p>The new statement will be registered and automatically closed when
0464: * this <code>DbConnection</code> cleans up. It is recommended though to
0465: * manually close the statement when it's not needed anymore for sensible
0466: * resource preservation.
0467: * <p>If an exception is thrown, this <code>DbConnection</code> is
0468: * automatically closed and if it's part of a pool, all the other
0469: * connections are closed too and the pool is set up again. Also, any
0470: * ongoing transaction will be rolled-back automatically.
0471: *
0472: * @param sql a <code>String</code> instance with the SQL that is used to
0473: * set up the prepared statement
0474: * @param autoGeneratedKeys a flag indicating whether auto-generated keys
0475: * should be returned; one of <code>Statement.RETURN_GENERATED_KEYS</code>
0476: * or <code>Statement.NO_GENERATED_KEYS</code>
0477: * @return a new <code>DbPreparedStatement</code> instance
0478: * @exception DatabaseException when an exception has occurred during the
0479: * creation of the <code>DbPreparedStatement</code> instance
0480: * @see com.uwyn.rife.database.DbPreparedStatement
0481: * @see #createStatement()
0482: * @see #getPreparedStatement(Query)
0483: * @since 1.0
0484: */
0485: public DbPreparedStatement getPreparedStatement(String sql,
0486: int autoGeneratedKeys) throws DatabaseException {
0487: if (null == sql)
0488: throw new IllegalArgumentException("sql can't be null.");
0489: if (0 == sql.length())
0490: throw new IllegalArgumentException("sql can't be empty.");
0491:
0492: try {
0493: detectCleanup();
0494:
0495: PreparedStatement prepared_statement = mConnection
0496: .prepareStatement(sql, autoGeneratedKeys);
0497:
0498: synchronized (this ) {
0499: DbPreparedStatement db_prepared_statement = new DbPreparedStatement(
0500: this , sql, prepared_statement);
0501: mStatements.add(db_prepared_statement);
0502:
0503: return db_prepared_statement;
0504: }
0505: } catch (SQLException e) {
0506: handleException();
0507: throw new PreparedStatementCreationErrorException(
0508: mDatasource, e);
0509: }
0510: }
0511:
0512: /**
0513: * Creates a new <code>DbPreparedStatement</code> instance for this
0514: * connection from a <code>Query</code> instance. Thanks to the additional
0515: * meta-data that's stored in a <code>Query</code> object, it's possible
0516: * to use the additional features that the
0517: * <code>DbPreparedStatement</code> provides on top of regular JDBC
0518: * methods.
0519: * <p>The new statement will be registered and automatically closed when
0520: * this <code>DbConnection</code> cleans up. It is recommended though to
0521: * manually close the statement when it's not needed anymore for sensible
0522: * resource preservation.
0523: * <p>If an exception is thrown, this <code>DbConnection</code> is
0524: * automatically closed and if it's part of a pool, all the other
0525: * connections are closed too and the pool is set up again. Also, any
0526: * ongoing transaction will be rolled-back automatically.
0527: *
0528: * @param query a <code>Query</code> instance that is used to set up the
0529: * prepared statement
0530: * @return a new <code>DbPreparedStatement</code> instance
0531: * @exception DatabaseException when an exception has occurred during the
0532: * creation of the <code>DbPreparedStatement</code> instance
0533: * @see com.uwyn.rife.database.DbPreparedStatement
0534: * @see #createStatement()
0535: * @see #getPreparedStatement(String)
0536: * @since 1.0
0537: */
0538: public DbPreparedStatement getPreparedStatement(Query query)
0539: throws DatabaseException {
0540: if (null == query)
0541: throw new IllegalArgumentException("query can't be null.");
0542: if (null == query.getSql())
0543: throw new IllegalArgumentException("query can't be empty.");
0544:
0545: try {
0546: detectCleanup();
0547:
0548: String sql = query.getSql();
0549: PreparedStatement prepared_statement = mConnection
0550: .prepareStatement(sql);
0551:
0552: synchronized (this ) {
0553: DbPreparedStatement db_prepared_statement = new DbPreparedStatement(
0554: this , query, prepared_statement);
0555: mStatements.add(db_prepared_statement);
0556:
0557: return db_prepared_statement;
0558: }
0559: } catch (SQLException e) {
0560: handleException();
0561: throw new PreparedStatementCreationErrorException(
0562: mDatasource, e);
0563: }
0564: }
0565:
0566: /**
0567: * Creates a new <code>DbPreparedStatement</code> instance for this
0568: * connection from a <code>Query</code> instance that has the capability
0569: * to retrieve auto-generated keys. The given constant tells the driver
0570: * whether it should make auto-generated keys available for retrieval.
0571: * This parameter is ignored if the SQL statement is not an INSERT
0572: * statement.
0573: * <p>Thanks to the additional meta-data that's stored in a
0574: * <code>Query</code> object, it's possible to use the additional features
0575: * that the <code>DbPreparedStatement</code> provides on top of regular
0576: * JDBC methods.
0577: * <p>The new statement will be registered and automatically closed when
0578: * this <code>DbConnection</code> cleans up. It is recommended though to
0579: * manually close the statement when it's not needed anymore for sensible
0580: * resource preservation.
0581: * <p>If an exception is thrown, this <code>DbConnection</code> is
0582: * automatically closed and if it's part of a pool, all the other
0583: * connections are closed too and the pool is set up again. Also, any
0584: * ongoing transaction will be rolled-back automatically.
0585: *
0586: * @param query a <code>Query</code> instance that is used to set up the
0587: * prepared statement
0588: * @param autoGeneratedKeys a flag indicating whether auto-generated keys
0589: * should be returned; one of <code>Statement.RETURN_GENERATED_KEYS</code>
0590: * or <code>Statement.NO_GENERATED_KEYS</code>
0591: * @return a new <code>DbPreparedStatement</code> instance
0592: * @exception DatabaseException when an exception has occurred during the
0593: * creation of the <code>DbPreparedStatement</code> instance
0594: * @see com.uwyn.rife.database.DbPreparedStatement
0595: * @see #createStatement()
0596: * @see #getPreparedStatement(String)
0597: * @since 1.0
0598: */
0599: public DbPreparedStatement getPreparedStatement(Query query,
0600: int autoGeneratedKeys) throws DatabaseException {
0601: if (null == query)
0602: throw new IllegalArgumentException("query can't be null.");
0603: if (null == query.getSql())
0604: throw new IllegalArgumentException("query can't be empty.");
0605:
0606: try {
0607: detectCleanup();
0608:
0609: String sql = query.getSql();
0610: PreparedStatement prepared_statement = mConnection
0611: .prepareStatement(sql, autoGeneratedKeys);
0612:
0613: synchronized (this ) {
0614: DbPreparedStatement db_prepared_statement = new DbPreparedStatement(
0615: this , query, prepared_statement);
0616: mStatements.add(db_prepared_statement);
0617:
0618: return db_prepared_statement;
0619: }
0620: } catch (SQLException e) {
0621: handleException();
0622: throw new PreparedStatementCreationErrorException(
0623: mDatasource, e);
0624: }
0625: }
0626:
0627: /**
0628: * Creates a new <code>DbPreparedStatement</code> instance for this
0629: * connection from a <code>Query</code> instance that will generate
0630: * <code>ResultSet</code> objects with the given type, concurrency,
0631: * and holdability.
0632: * <p>
0633: * This method is the same as the <code>getPreparedStatement</code> method
0634: * above, but it allows the default result set
0635: * type, concurrency, and holdability to be overridden.
0636: * <p>Thanks to the additional meta-data that's stored in a
0637: * <code>Query</code> object, it's possible to use the additional features
0638: * that the <code>DbPreparedStatement</code> provides on top of regular
0639: * JDBC methods.
0640: * <p>The new statement will be registered and automatically closed when
0641: * this <code>DbConnection</code> cleans up. It is recommended though to
0642: * manually close the statement when it's not needed anymore for sensible
0643: * resource preservation.
0644: * <p>If an exception is thrown, this <code>DbConnection</code> is
0645: * automatically closed and if it's part of a pool, all the other
0646: * connections are closed too and the pool is set up again. Also, any
0647: * ongoing transaction will be rolled-back automatically.
0648: *
0649: * @param query a <code>Query</code> instance that is used to set up the
0650: * prepared statement
0651: * @param resultSetType one of the following <code>ResultSet</code>
0652: * constants:
0653: * <code>ResultSet.TYPE_FORWARD_ONLY</code>,
0654: * <code>ResultSet.TYPE_SCROLL_INSENSITIVE</code>, or
0655: * <code>ResultSet.TYPE_SCROLL_SENSITIVE</code>
0656: * @param resultSetConcurrency one of the following <code>ResultSet</code>
0657: * constants:
0658: * <code>ResultSet.CONCUR_READ_ONLY</code> or
0659: * <code>ResultSet.CONCUR_UPDATABLE</code>
0660: * @param resultSetHoldability one of the following <code>ResultSet</code>
0661: * constants:
0662: * <code>ResultSet.HOLD_CURSORS_OVER_COMMIT</code> or
0663: * <code>ResultSet.CLOSE_CURSORS_AT_COMMIT</code>
0664: * @return a new <code>DbPreparedStatement</code> instance, that will generate
0665: * <code>ResultSet</code> objects with the given type,
0666: * concurrency, and holdability
0667: * @exception DatabaseException when an exception has occurred during the
0668: * creation of the <code>DbPreparedStatement</code> instance
0669: * @see com.uwyn.rife.database.DbPreparedStatement
0670: * @see #createStatement()
0671: * @see #getPreparedStatement(String)
0672: * @since 1.2
0673: */
0674: public DbPreparedStatement getPreparedStatement(Query query,
0675: int resultSetType, int resultSetConcurrency,
0676: int resultSetHoldability) throws DatabaseException {
0677: if (null == query)
0678: throw new IllegalArgumentException("query can't be null.");
0679: if (null == query.getSql())
0680: throw new IllegalArgumentException("query can't be empty.");
0681:
0682: try {
0683: detectCleanup();
0684:
0685: String sql = query.getSql();
0686: PreparedStatement prepared_statement = mConnection
0687: .prepareStatement(sql, resultSetType,
0688: resultSetConcurrency, resultSetHoldability);
0689:
0690: synchronized (this ) {
0691: DbPreparedStatement db_prepared_statement = new DbPreparedStatement(
0692: this , query, prepared_statement);
0693: mStatements.add(db_prepared_statement);
0694:
0695: return db_prepared_statement;
0696: }
0697: } catch (SQLException e) {
0698: handleException();
0699: throw new PreparedStatementCreationErrorException(
0700: mDatasource, e);
0701: }
0702: }
0703:
0704: /**
0705: * Removes a <code>DbStatement</code> instance from the collection of
0706: * managed statements. If the statement is not present, no error or
0707: * exception is thrown.
0708: *
0709: * @param statement the <code>DbStatement</code> that has to be removed.
0710: * @since 1.0
0711: */
0712: void releaseStatement(DbStatement statement) {
0713: synchronized (this ) {
0714: mStatements.remove(statement);
0715: }
0716: }
0717:
0718: /**
0719: * Indicates whether the <code>Datasource</code> of this
0720: * <code>DbConnection</code> supports transactions or not.
0721: * <p>This information is only retrieved once and cached for the rest of
0722: * the lifetime of this connection.
0723: * <p>If an exception is thrown, this <code>DbConnection</code> is
0724: * automatically closed and if it's part of a pool, all the other
0725: * connections are closed too and the pool is set up again. Also, any
0726: * ongoing transaction will be rolled-back automatically.
0727: *
0728: * @return <code>true</code> if the <code>Datasource</code> supports
0729: * transactions; or
0730: * <p><code>false</code> if the <code>Datasource</code> doesn't support
0731: * transactions.
0732: * @exception DatabaseException when an error occurred during the
0733: * verification of the transaction support
0734: * @see #beginTransaction()
0735: * @see #commit()
0736: * @see #rollback()
0737: * @see #isFree()
0738: * @see #isTransactionValidForThread()
0739: * @since 1.0
0740: */
0741: public boolean supportsTransactions() throws DatabaseException {
0742: try {
0743: synchronized (this ) {
0744: detectCleanup();
0745:
0746: if (TRANSACTIONS_SUPPORT_UNKNOWN == mSupportsTransactions) {
0747: if (mConnection.getMetaData()
0748: .supportsTransactions()) {
0749: mSupportsTransactions = TRANSACTIONS_SUPPORTED;
0750: } else {
0751: mSupportsTransactions = TRANSACTIONS_UNSUPPORTED;
0752: }
0753: }
0754:
0755: return TRANSACTIONS_SUPPORTED == mSupportsTransactions;
0756:
0757: }
0758: } catch (SQLException e) {
0759: handleException();
0760: throw new TransactionSupportCheckErrorException(
0761: mDatasource, e);
0762: }
0763: }
0764:
0765: /**
0766: * <p><strong>Warning:</strong> only use the raw transaction methods if
0767: * you really know what you're doing. It's almost always better to use the
0768: * {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
0769: * method of the <code>DbQueryManager</code> class instead.
0770: * <p>Starts a new transaction if the <code>Datasource</code> supports it.
0771: * <p>If an exception is thrown, this <code>DbConnection</code> is
0772: * automatically closed and if it's part of a pool, all the other
0773: * connections are closed too and the pool is set up again. Also, any
0774: * ongoing transaction will be rolled-back automatically.
0775: *
0776: * @return <code>true</code> if the transaction was successfully started;
0777: * or
0778: * <p><code>false</code> if the <code>Datasource</code> doesn't support
0779: * transactions, or if a transaction is already active on this
0780: * <code>DbConnection</code>.
0781: * @exception DatabaseException when an error occurred during the creation
0782: * of the new transaction, or when the active transaction has timed-out.
0783: * @see DbQueryManager#inTransaction(DbTransactionUser)
0784: * @see #supportsTransactions()
0785: * @see #commit()
0786: * @see #rollback()
0787: * @see #isFree()
0788: * @see #isTransactionValidForThread()
0789: * @since 1.0
0790: */
0791: public boolean beginTransaction() throws DatabaseException {
0792: // if the datasource doesn't support transactions,
0793: // don't start any
0794: if (!supportsTransactions()) {
0795: return false;
0796: }
0797:
0798: synchronized (this ) {
0799: // check if a thread has already got a hold of this connection
0800: if (hasTransactionThread()) {
0801: // if it's still active, it's impossible to begin a new
0802: // transaction
0803: return false;
0804: }
0805:
0806: // setup the new transaction
0807: mTransactionThread = Thread.currentThread();
0808: }
0809:
0810: try {
0811: detectCleanup();
0812:
0813: mConnection.setAutoCommit(false);
0814: } catch (SQLException e) {
0815:
0816: if (mTransactionThread != null) {
0817: mTransactionThread = null;
0818: }
0819: handleException();
0820: throw new TransactionBeginErrorException(mDatasource, e);
0821: }
0822:
0823: mDatasource.getPool().registerThreadConnection(
0824: mTransactionThread, this );
0825:
0826: return true;
0827: }
0828:
0829: /**
0830: * <p><strong>Warning:</strong> only use the raw transaction methods if
0831: * you really know what you're doing. It's almost always better to use the
0832: * {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
0833: * method of the <code>DbQueryManager</code> class instead.
0834: * <p>Commits an active transaction.
0835: * <p>All transaction-related resources are cleared and all the threads
0836: * that are waiting for the transaction to terminate are woken up.
0837: * <p>If an exception is thrown, this <code>DbConnection</code> is
0838: * automatically closed and if it's part of a pool, all the other
0839: * connections are closed too and the pool is set up again. Also, any
0840: * ongoing transaction will be rolled-back automatically.
0841: *
0842: * @return <code>true</code> if the transaction was successfully
0843: * committed; or
0844: * <p><code>false</code> if the <code>Datasource</code> doesn't support
0845: * transactions, or when no transaction is active on this
0846: * <code>DbConnection</code>, or when the executing thread isn't the
0847: * thread that began the transaction.
0848: * @exception DatabaseException when an error occurred during the commit
0849: * of the active transaction, or when the active transaction has
0850: * timed-out.
0851: * @see DbQueryManager#inTransaction(DbTransactionUser)
0852: * @see #supportsTransactions()
0853: * @see #beginTransaction()
0854: * @see #rollback()
0855: * @see #isFree()
0856: * @see #isTransactionValidForThread()
0857: * @since 1.0
0858: */
0859: public boolean commit() throws DatabaseException {
0860: // if the datasource doesn't support transactions,
0861: // it's impossible to commit one
0862: if (!supportsTransactions()) {
0863: return false;
0864: }
0865:
0866: synchronized (this ) {
0867: // if the transaction isn't valid for the current thread
0868: // refuse to commit it
0869: if (!isTransactionValidForThread()) {
0870: return false;
0871: }
0872: }
0873:
0874: try {
0875: detectCleanup();
0876:
0877: mConnection.commit();
0878: mConnection.setAutoCommit(true);
0879: } catch (SQLException e) {
0880: handleException();
0881: throw new TransactionCommitErrorException(mDatasource, e);
0882: } finally {
0883: synchronized (this ) {
0884: if (mTransactionThread != null) {
0885: mTransactionThread = null;
0886: }
0887:
0888: this .notifyAll();
0889: }
0890:
0891: mDatasource.getPool().unregisterThreadConnection(
0892: Thread.currentThread());
0893: }
0894:
0895: return true;
0896: }
0897:
0898: /**
0899: * <p><strong>Warning:</strong> only use the raw transaction methods if
0900: * you really know what you're doing. It's almost always better to use the
0901: * {@link DbQueryManager#inTransaction(DbTransactionUser) inTransaction}
0902: * method of the <code>DbQueryManager</code> class instead.
0903: * <p>Rolls-back an active transaction.
0904: * <p>All transaction-related resources are cleared and all the threads
0905: * that are waiting for the transaction to terminate are woken up.
0906: * <p>If an exception is thrown, this <code>DbConnection</code> is
0907: * automatically closed and if it's part of a pool, all the other
0908: * connections are closed too and the pool is set up again. Also, any
0909: * ongoing transaction will be rolled-back automatically.
0910: *
0911: * @return <code>true</code> if the transaction was successfully
0912: * rolled-back; or
0913: * <p><code>false</code> if the <code>Datasource</code> doesn't support
0914: * transactions, or when no transaction is active on this
0915: * <code>DbConnection</code>, or when the executing thread isn't the
0916: * thread that began the transaction.
0917: * @exception DatabaseException when an error occurred during the rollback
0918: * of the active transaction, or when the active transaction has
0919: * timed-out.
0920: * @see DbQueryManager#inTransaction(DbTransactionUser)
0921: * @see #supportsTransactions()
0922: * @see #beginTransaction()
0923: * @see #commit()
0924: * @see #isFree()
0925: * @see #isTransactionValidForThread()
0926: * @since 1.0
0927: */
0928: public boolean rollback() throws DatabaseException {
0929: // if the datasource doesn't support transactions,
0930: // it's impossible to roll one back
0931: if (!supportsTransactions()) {
0932: return false;
0933: }
0934:
0935: synchronized (this ) {
0936: // if the transaction isn't valid for the current thread
0937: // refuse to roll it back
0938: if (!isTransactionValidForThread()) {
0939: return false;
0940: }
0941: }
0942:
0943: try {
0944: detectCleanup();
0945:
0946: mConnection.rollback();
0947: mConnection.setAutoCommit(true);
0948: } catch (SQLException e) {
0949: handleException();
0950: throw new TransactionRollbackErrorException(mDatasource, e);
0951: } finally {
0952: synchronized (this ) {
0953: if (mTransactionThread != null) {
0954: mTransactionThread = null;
0955: }
0956:
0957: this .notifyAll();
0958: }
0959:
0960: mDatasource.getPool().unregisterThreadConnection(
0961: Thread.currentThread());
0962: }
0963:
0964: return true;
0965: }
0966:
0967: /**
0968: * Indicates whether this <code>DbConnection</code> is free to execute
0969: * statements for the current thread.
0970: *
0971: * @return <code>true</code> if a statement can be executed by the current
0972: * thread on this <code>DbConnection</code>; or
0973: * <p><code>false</code> if the connection is closed or when a transaction
0974: * is already active on this <code>DbConnection</code> for another thread.
0975: * @see #supportsTransactions()
0976: * @see #beginTransaction()
0977: * @see #commit()
0978: * @see #rollback()
0979: * @see #isTransactionValidForThread()
0980: * @since 1.0
0981: */
0982: public boolean isFree() {
0983: synchronized (this ) {
0984: if (isCleanedUp()) {
0985: return false;
0986: }
0987:
0988: if (!hasTransactionThread()) {
0989: return true;
0990: }
0991:
0992: if (isTransactionValidForThread()) {
0993: return true;
0994: }
0995:
0996: return false;
0997: }
0998: }
0999:
1000: /**
1001: * Indicates whether this connections has an active transaction thread.
1002: *
1003: * @return <code>true</code> is an active transaction thread is present;
1004: * or
1005: * <p><code>false</code> otherwise.
1006: */
1007: private boolean hasTransactionThread() {
1008: return mTransactionThread != null;
1009: }
1010:
1011: /**
1012: * Indicates whether the current thread has a valid transaction going on
1013: * for the execution of statements.
1014: * <p>If an exception is thrown, this <code>DbConnection</code> is
1015: * automatically closed and if it's part of a pool, all the other
1016: * connections are closed too and the pool is set up again.
1017: *
1018: * @return <code>true</code> if a transaction is active that can be used
1019: * by the current thread; or
1020: * <p><code>false</code> if the connection is closed, doesn't support
1021: * transactions, has no active transaction or has a transaction that was
1022: * started by another thread.
1023: * @exception DatabaseException when errors occurred during the
1024: * verification of the connection's open status and support for
1025: * transactions
1026: * @see #supportsTransactions()
1027: * @see #beginTransaction()
1028: * @see #commit()
1029: * @see #rollback()
1030: * @see #isFree()
1031: * @since 1.0
1032: */
1033: public boolean isTransactionValidForThread() {
1034: synchronized (this ) {
1035: if (!hasTransactionThread()) {
1036: return false;
1037: }
1038:
1039: return mTransactionThread == Thread.currentThread();
1040:
1041: }
1042: }
1043:
1044: /**
1045: * Indicates whether this <code>DbConnection</code>'s connection to the
1046: * database is closed.
1047: * <p>If an exception is thrown, this <code>DbConnection</code> is
1048: * automatically cleaned up. Also, any ongoing transaction will be
1049: * rolled-back automatically.
1050: *
1051: * @return <code>true</code> when this <code>DbConnection</code> is
1052: * closed; or
1053: * <p><code>false</code> if it's connected.
1054: * @exception DatabaseException when an error occurred during the
1055: * verification of the JDBC connection's closed status
1056: * @since 1.0
1057: */
1058: public boolean isClosed() throws DatabaseException {
1059: try {
1060: if (null == mConnection) {
1061: return true;
1062: }
1063:
1064: return mConnection.isClosed();
1065: } catch (SQLException e) {
1066: cleanup();
1067: throw new ConnectionStatusErrorException(mDatasource, e);
1068: }
1069: }
1070:
1071: /**
1072: * Retrieves a <code>DatabaseMetaData</code> object that contains metadata
1073: * about the database to which this <code>DbConnection</code> object
1074: * represents a connection. The metadata includes information about the
1075: * database's tables, its supported SQL grammar, its stored procedures,
1076: * the capabilities of this connection, and so on.
1077: * <p>If an exception is thrown, this <code>DbConnection</code> is
1078: * automatically closed and if it's part of a pool, all the other
1079: * connections are closed too and the pool is set up again. Also, any
1080: * ongoing transaction will be rolled-back automatically.
1081: *
1082: * @return a <code>DatabaseMetaData</code> object for this
1083: * <code>DbConnection</code> instance; or
1084: * <p><code>null</code> if the <code>DbConnection</code> instance is not
1085: * connected.
1086: * @exception DatabaseException if a database access error occurs
1087: */
1088: public DatabaseMetaData getMetaData() throws DatabaseException {
1089: try {
1090: detectCleanup();
1091:
1092: return mConnection.getMetaData();
1093: } catch (SQLException e) {
1094: handleException();
1095: throw new ConnectionMetaDataErrorException(mDatasource, e);
1096: }
1097: }
1098:
1099: /**
1100: * Attempts to change the transaction isolation level for this
1101: * <code>DbConnection</code> object to the one given. The constants
1102: * defined in the interface <code>Connection</code> are the possible
1103: * transaction isolation levels.
1104: *
1105: * @param level transaction isolation level constant defined in the <code>{@link
1106: * java.sql.Connection Connection}</code> interface
1107: * @exception DatabaseException if a database access error occurs
1108: * @see java.sql.Connection
1109: */
1110: public void setTransactionIsolation(int level)
1111: throws DatabaseException {
1112: try {
1113: detectCleanup();
1114:
1115: mConnection.setTransactionIsolation(level);
1116: } catch (SQLException e) {
1117: handleException();
1118: throw new ConnectionMetaDataErrorException(mDatasource, e);
1119: }
1120: }
1121:
1122: /**
1123: * Ensures that this <code>DbConnection</code> is correctly cleaned-up
1124: * when it's garbage collected.
1125: *
1126: * @exception Throwable if an error occurred during the finalization
1127: * @since 1.0
1128: */
1129: protected void finalize() throws Throwable {
1130: synchronized (this ) {
1131: cleanup();
1132: this .notifyAll();
1133: }
1134:
1135: super .finalize();
1136: }
1137:
1138: /**
1139: * Simply clones the instance with the default clone method. This creates
1140: * a shallow copy of all fields and the clone will in fact just be another
1141: * reference to the same underlying data. The independence of each cloned
1142: * instance is consciously not respected since they rely on resources that
1143: * can't be cloned.
1144: *
1145: * @since 1.0
1146: */
1147: public Object clone() {
1148: try {
1149: return super .clone();
1150: } catch (CloneNotSupportedException e) {
1151: // this should never happen
1152: Logger.getLogger("com.uwyn.rife.database").severe(
1153: ExceptionUtils.getExceptionStackTrace(e));
1154: return null;
1155: }
1156: }
1157: }
|