/* * JBoss, Home of Professional Open Source * Copyright 2006, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. * See the copyright.txt in the distribution for a * full listing of individual contributors. * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * * (C) 2005-2009, * @author JBoss Inc. */ /* * Copyright (C) 1998, 1999, 2000, 2001, * * Arjuna Solutions Limited, * Newcastle upon Tyne, * Tyne and Wear, * UK. * * $Id: ConnectionImple.java 2342 2006-03-30 13:06:17Z $ */ package com.arjuna.ats.internal.jdbc; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import javax.sql.XAConnection; import javax.sql.XADataSource; import javax.transaction.RollbackException; import javax.transaction.Status; import javax.transaction.SystemException; import javax.transaction.xa.XAResource; import com.arjuna.ats.internal.jdbc.drivers.modifiers.ConnectionModifier; import com.arjuna.ats.internal.jdbc.drivers.modifiers.ModifierFactory; import com.arjuna.ats.jdbc.TransactionalDriver; import com.arjuna.ats.jdbc.common.jdbcPropertyManager; import com.arjuna.ats.jdbc.logging.jdbcLogger; import com.arjuna.ats.jta.xa.RecoverableXAConnection; import com.arjuna.ats.jta.xa.XAModifier; /** * A transactional JDBC connection. This wraps the real connection and * registers it with the transaction at appropriate times to ensure that all * worked performed by it may be committed or rolled back. * * Once a connection is used within a transaction, that instance is bound to * that transaction for the duration. It can be used by any number of threads, * as long as they all have the same notion of the "current" transaction. When * the transaction terminates, the connection is freed for use in another * transaction. * * Applications must not use this class directly. * * @author Mark Little (mark@arjuna.com) * @version $Id: ConnectionImple.java 2342 2006-03-30 13:06:17Z $ * @since JTS 2.0. */ public class ConnectionImple implements Connection { public ConnectionImple(String dbName, Properties info) throws SQLException { if (jdbcLogger.logger.isTraceEnabled()) { jdbcLogger.logger.trace("ConnectionImple.ConnectionImple ( " + dbName + " )"); } String user = null; String passwd = null; String dynamic = null; Object xaDataSource = null; if (info != null) { user = info.getProperty(TransactionalDriver.userName, ""); passwd = info.getProperty(TransactionalDriver.password, ""); dynamic = info.getProperty(TransactionalDriver.dynamicClass); xaDataSource = info.get(TransactionalDriver.XADataSource); } if (xaDataSource != null) { _transactionalDriverXAConnectionConnection = new ProvidedXADataSourceConnection(dbName, user, passwd, (XADataSource) xaDataSource, this); } else if ((dynamic == null) || (dynamic.equals(""))) { _transactionalDriverXAConnectionConnection = new IndirectRecoverableConnection(dbName, user, passwd, this); } else { _transactionalDriverXAConnectionConnection = new DirectRecoverableConnection(dbName, user, passwd, dynamic, this); } /* * Is there any "modifier" we are required to work with? */ _theModifier = null; _theConnection = null; } public ConnectionImple(String dbName, String user, String passwd) throws SQLException { this(dbName, user, passwd, null, null); } public ConnectionImple(String dbName, String user, String passwd, String dynamic, Object xaDataSource) throws SQLException { if (jdbcLogger.logger.isTraceEnabled()) { jdbcLogger.logger.trace("ConnectionImple.ConnectionImple ( " + dbName + ", " + user + ", " + passwd + ", " + dynamic + " )"); } if (xaDataSource != null) { _transactionalDriverXAConnectionConnection = new ProvidedXADataSourceConnection(dbName, user, passwd, (XADataSource) xaDataSource, this); } else if ((dynamic == null) || (dynamic.equals(""))) { _transactionalDriverXAConnectionConnection = new IndirectRecoverableConnection(dbName, user, passwd, this); } else { _transactionalDriverXAConnectionConnection = new DirectRecoverableConnection(dbName, user, passwd, dynamic, this); } /* * Any "modifier" required to work with? */ _theModifier = null; _theConnection = null; } public Statement createStatement() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createStatement(); } public Statement createStatement(int rs, int rc) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createStatement(rs, rc); } public PreparedStatement prepareStatement(String sql) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql); } public PreparedStatement prepareStatement(String sql, int rs, int rc) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql, rs, rc); } public CallableStatement prepareCall(String sql) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareCall(sql); } public CallableStatement prepareCall(String sql, int rs, int rc) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareCall(sql, rs, rc); } public String nativeSQL(String sql) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().nativeSQL(sql); } public Map getTypeMap() throws SQLException { return getConnection().getTypeMap(); } /* public void setTypeMap(Map map) throws SQLException { getConnection().setTypeMap(map); } */ /** * Not allowed if within a transaction. */ public void setAutoCommit(boolean autoCommit) throws SQLException { if (transactionRunning()) { if (autoCommit) throw new SQLException(jdbcLogger.i18NLogger.get_autocommit()); } else { getConnection().setAutoCommit(autoCommit); } } public boolean getAutoCommit() throws SQLException { return getConnection().getAutoCommit(); } public void commit() throws SQLException { /* * If there is a transaction running, then it cannot be terminated via * the driver - the user must go through current. */ if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_commiterror()); } else getConnection().commit(); } public void rollback() throws SQLException { if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_aborterror()); } else getConnection().rollback(); } /* * This needs to be reworked in light of experience and requirements. */ public void close() throws SQLException { boolean delayClose = false; try { /* * Delist resource if within a transaction. */ javax.transaction.TransactionManager tm = com.arjuna.ats.jta.TransactionManager .transactionManager(); javax.transaction.Transaction tx = tm.getTransaction(); /* * Don't delist if transaction not running. Rely on exception for * this. Also only delist if the transaction is the one the * connection is enlisted with! */ if (tx != null) { if (_transactionalDriverXAConnectionConnection.validTransaction(tx)) { XAResource xares = _transactionalDriverXAConnectionConnection.getResource(); if ((tx.getStatus() == Status.STATUS_ACTIVE && !tx.delistResource(xares, XAResource.TMSUCCESS)) || (tx.getStatus() == Status.STATUS_MARKED_ROLLBACK && !tx.delistResource(xares, XAResource.TMFAIL))) throw new SQLException( jdbcLogger.i18NLogger.get_delisterror()); if (tx.getStatus() == Status.STATUS_ACTIVE) { getModifier(); if (_theModifier == null) { jdbcLogger.i18NLogger.info_closingconnectionnull(_theConnection.toString()); // no indication about connections, so assume close immediately /* * Don't return just yet. Drop through bottom of these clauses and * close _theConnection and _recoveryConnection. * * delayClose is false at this point. * * JBTM-789. */ } else { if (((ConnectionModifier) _theModifier).supportsMultipleConnections()) { /* * We can't close the connection until the transaction has * terminated, so register a Synchronization here. */ jdbcLogger.i18NLogger.debug_closingconnection(_theConnection.toString()); delayClose = true; } } if (delayClose) { tx.registerSynchronization(new ConnectionSynchronization(this)); } } } else throw new SQLException(jdbcLogger.i18NLogger.get_closeerrorinvalidtx(tx.toString())); } } catch (IllegalStateException ex) { // transaction not running, so ignore. } catch (SQLException sqle) { throw sqle; } catch (Exception e1) { SQLException sqlException = new SQLException(jdbcLogger.i18NLogger.get_closeerror()); sqlException.initCause(e1); throw sqlException; } finally { if (!delayClose) { closeImpl(); } } } void closeImpl() throws SQLException { try { ConnectionManager.remove(this); if (_theConnection != null && !_theConnection.isClosed()) { _theConnection.close(); } if (_transactionalDriverXAConnectionConnection != null) { _transactionalDriverXAConnectionConnection.closeCloseCurrentConnection(); } } finally { _theConnection = null; _transactionalDriverXAConnectionConnection = null; } } public boolean isClosed() throws SQLException { /* * A connection may appear closed to a thread if another thread has * bound it to a different transaction. */ if (_transactionalDriverXAConnectionConnection == null) return true; // closeImpl was explicitly called else if (_theConnection == null) return false; // not opened yet. // TODO why don't we return true here else return _theConnection.isClosed(); } public DatabaseMetaData getMetaData() throws SQLException { return getConnection().getMetaData(); } /** * Can only set readonly before we use the connection in a given * transaction! */ public void setReadOnly(boolean ro) throws SQLException { if (!_transactionalDriverXAConnectionConnection.inuse()) { getConnection().setReadOnly(ro); } else throw new SQLException(jdbcLogger.i18NLogger.get_setreadonly()); } public boolean isReadOnly() throws SQLException { return getConnection().isReadOnly(); } public void setCatalog(String cat) throws SQLException { checkTransaction(); registerDatabase(); getConnection().setCatalog(cat); } public String getCatalog() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().getCatalog(); } public void setTransactionIsolation(int iso) throws SQLException { checkTransaction(); /* * if (iso != Connection.TRANSACTION_SERIALIZABLE) throw new * SQLException(jdbcLogger.loggerI18N.getString.getString("com.arjuna.ats.internal.jdbc.stateerror")+"Connection.TRANSACTION_SERIALIZABLE"); */ getConnection().setTransactionIsolation(iso); } public int getTransactionIsolation() throws SQLException { return getConnection().getTransactionIsolation(); } public SQLWarning getWarnings() throws SQLException { return getConnection().getWarnings(); } public void clearWarnings() throws SQLException { getConnection().clearWarnings(); } /** * @return the Arjuna specific recovery connection information. This should * not be used by anything other than Arjuna. */ public final RecoverableXAConnection recoveryConnection() { if (_transactionalDriverXAConnectionConnection instanceof RecoverableXAConnection) { return (RecoverableXAConnection) _transactionalDriverXAConnectionConnection; } else { return null; } } /* * ******************************************************************* * * JDBC 3.0 section */ public void setTypeMap(Map<String, Class<?>> map) throws SQLException { getConnection().setTypeMap(map); } public void setHoldability(int holdability) throws SQLException { checkTransaction(); registerDatabase(); getConnection().setHoldability(holdability); } public int getHoldability() throws SQLException { return getConnection().getHoldability(); } public Savepoint setSavepoint() throws SQLException { if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_setsavepointerror()); } else { return getConnection().setSavepoint(); } } public Savepoint setSavepoint(String name) throws SQLException { if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_setsavepointerror()); } else { return getConnection().setSavepoint(name); } } // The JDBC 3.0 spec (section 12.4) prohibits calling setSavepoint indide an XA tx. // It does not explicitly disallow calling rollback(savepoint) or releaseSavepoint(savepoint) // but allowing them does not make a whole lot of sense, so we don't: public void rollback(Savepoint savepoint) throws SQLException { if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_rollbacksavepointerror()); } else { getConnection().rollback(savepoint); } } public void releaseSavepoint(Savepoint savepoint) throws SQLException { if (transactionRunning()) { throw new SQLException(jdbcLogger.i18NLogger.get_releasesavepointerror()); } else { getConnection().releaseSavepoint(savepoint); } } public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); } public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql, autoGeneratedKeys); } public PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql, columnIndexes); } public PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().prepareStatement(sql, columnNames); } /* * end of the JDBC 3.0 section * ******************************************************************* */ /* * ******************************************************************* * * JDBC 4.0 method section. */ public Clob createClob() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createClob(); } public Blob createBlob() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createBlob(); } public NClob createNClob() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createNClob(); } public SQLXML createSQLXML() throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createSQLXML(); } public boolean isValid(int timeout) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().isValid(timeout); } public String getClientInfo(String name) throws SQLException { return getConnection().getClientInfo(name); } public Properties getClientInfo() throws SQLException { return getConnection().getClientInfo(); } public void setClientInfo(String name, String value) throws SQLClientInfoException { try { getConnection().setClientInfo(name, value); } catch(SQLException e) { throw new SQLClientInfoException("setClientInfo : getConnection failed", null, e); } } public void setClientInfo(Properties properties) throws SQLClientInfoException { try { getConnection().setClientInfo(properties); } catch(SQLException e) { throw new SQLClientInfoException("setClientInfo : getConnection failed", null, e); } } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createArrayOf(typeName, elements); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { checkTransaction(); registerDatabase(); return getConnection().createStruct(typeName, attributes); } public <T> T unwrap(Class<T> iface) throws SQLException { if (iface != null) { if (iface.isInstance(this)) { return (T) this; } else { Connection conn = getConnection(); if (conn != null) { if (iface.isInstance(conn)) { return (T) conn; } else if(conn.isWrapperFor(iface)) { return conn.unwrap(iface); } } } } return null; } public boolean isWrapperFor(Class<?> iface) throws SQLException { if (iface != null) { if (iface.isInstance(this)) { return true; } else { Connection conn = getConnection(); if (conn != null) { if (iface.isInstance(conn)) { return true; } else { return conn.isWrapperFor(iface); } } } } return false; } /* * end of the JDBC 4.0 section * ******************************************************************* */ /* * ******************************************************************* * * Java 7 method section. */ //@Override public void setSchema(String schema) throws SQLException { throw new SQLException(); } //@Override public String getSchema() throws SQLException { throw new SQLException(); } //@Override public void abort(Executor executor) throws SQLException { throw new SQLException(); } //@Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { throw new SQLException(); } //@Override public int getNetworkTimeout() throws SQLException { throw new SQLException(); } /* * end of the Java 7 section * ******************************************************************* */ /** * @return the XAResource associated with the current XAConnection. */ protected final XAResource getXAResource() { try { return _transactionalDriverXAConnectionConnection.getResource(); } catch (Exception e) { return null; } } /** * Remove this connection so that we have to get another one when asked. * Some drivers allow connections to be reused once any transactions have * finished with them. */ final void reset() { try { closeImpl(); } catch (Exception ex) { } } final java.sql.Connection getConnection() throws SQLException { if (_theConnection != null && !_theConnection.isClosed()) return _theConnection; XAConnection xaConn = _transactionalDriverXAConnectionConnection.getConnection(); if (xaConn != null) { _theConnection = xaConn.getConnection(); try { getModifier(); if (_theModifier != null) { ((ConnectionModifier) _theModifier).setIsolationLevel( _theConnection, _currentIsolationLevel); } } catch (SQLException ex) { throw ex; } catch (Exception e) { jdbcLogger.i18NLogger.warn_isolationlevelfailset("ConnectionImple.getConnection", e); SQLException sqlException = new SQLException(jdbcLogger.i18NLogger.get_conniniterror()); sqlException.initCause(e); throw sqlException; } return _theConnection; } else return null; } final ConnectionControl connectionControl() { return (ConnectionControl) _transactionalDriverXAConnectionConnection; } protected final boolean transactionRunning() throws SQLException { try { if (com.arjuna.ats.jta.TransactionManager.transactionManager() .getTransaction() != null) { return true; } else { return false; } } catch (Exception e) { SQLException sqlException = new SQLException(e.toString()); sqlException.initCause(e); throw sqlException; } } /** * Whenever a JDBC call is invoked on us we get an XAResource and try to * register it with the transaction. If the same thread causes this to * happen many times within the same transaction then we will currently * attempt to get and register many redundant XAResources for it. The JTA * implementation will detect this and ignore all but the first for each * thread. However, a further optimisation would be to trap such calls here * and not do a registration at all. This would require the connection * object to be informed whenever a transaction completes so that it could * flush its cache of XAResources though. */ protected final synchronized void registerDatabase() throws SQLException { if (jdbcLogger.logger.isTraceEnabled()) { jdbcLogger.logger.trace("ConnectionImple.registerDatabase ()"); } Connection theConnection = getConnection(); if (theConnection != null) { XAResource xares = null; try { javax.transaction.TransactionManager tm = com.arjuna.ats.jta.TransactionManager .transactionManager(); javax.transaction.Transaction tx = tm.getTransaction(); if (tx == null) return; /* * Already enlisted with this transaction? */ if (!_transactionalDriverXAConnectionConnection.setTransaction(tx)) throw new SQLException( jdbcLogger.i18NLogger.get_alreadyassociated() ); Object[] params; if (_theModifier != null) params = new Object[2]; else params = new Object[1]; params[com.arjuna.ats.jta.transaction.Transaction.XACONNECTION] = _transactionalDriverXAConnectionConnection; if (_theModifier != null) params[com.arjuna.ats.jta.transaction.Transaction.XAMODIFIER] = (XAModifier) _theModifier; /* * Use our extended version of enlistResource. */ xares = _transactionalDriverXAConnectionConnection.getResource(); if (!((com.arjuna.ats.jta.transaction.Transaction) tx) .enlistResource(xares, params)) { /* * Failed to enlist, so mark transaction as rollback only. */ try { tx.setRollbackOnly(); } catch (Exception e) { jdbcLogger.i18NLogger.warn_rollbackerror("ConnectionImple.registerDatabase"); SQLException sqlException = new SQLException(e.toString()); sqlException.initCause(e); throw sqlException; } throw new SQLException( "ConnectionImple.registerDatabase - " + jdbcLogger.i18NLogger.get_enlistfailed()); } params = null; xares = null; tx = null; tm = null; } catch (RollbackException e1) { SQLException sqlException = new SQLException("ConnectionImple.registerDatabase - " + e1); sqlException.initCause(e1); throw sqlException; } catch (SystemException e2) { SQLException sqlException = new SQLException("ConnectionImple.registerDatabase - "+ e2); sqlException.initCause(e2); throw sqlException; } catch (SQLException e3) { throw e3; } catch (Exception e4) { SQLException sqlException = new SQLException(e4.toString()); sqlException.initCause(e4); throw sqlException; } } } protected final void checkTransaction() throws SQLException { if (jdbcLogger.logger.isTraceEnabled()) { jdbcLogger.logger.trace("ConnectionImple.checkTransaction ()"); } try { javax.transaction.TransactionManager tm = com.arjuna.ats.jta.TransactionManager .transactionManager(); javax.transaction.Transaction tx = tm.getTransaction(); if (tx == null) return; if (tx.getStatus() != Status.STATUS_ACTIVE) throw new SQLException(jdbcLogger.i18NLogger.get_inactivetransaction()); /* * Now check that we are not already associated with a transaction. */ if (!_transactionalDriverXAConnectionConnection.validTransaction(tx)) throw new SQLException( jdbcLogger.i18NLogger.get_alreadyassociatedcheck()); } catch (SQLException ex) { throw ex; } catch (Exception e3) { SQLException sqlException = new SQLException(jdbcLogger.i18NLogger.get_infoerror()); sqlException.initCause(e3); throw sqlException; } } private final void getModifier() { if (_theModifier == null) { try { DatabaseMetaData md = _theConnection.getMetaData(); String name = md.getDriverName(); int major = md.getDriverMajorVersion(); int minor = md.getDriverMinorVersion(); _theModifier = ModifierFactory.getModifier(name, major, minor); ((ConnectionControl) _transactionalDriverXAConnectionConnection) .setModifier((ConnectionModifier) _theModifier); } catch (Exception ex) { jdbcLogger.i18NLogger.warn_getmoderror(ex); } } } private TransactionalDriverXAConnection _transactionalDriverXAConnectionConnection; private java.lang.Object _theModifier; private Connection _theConnection; private static final int defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE; private static final int _currentIsolationLevel = jdbcPropertyManager.getJDBCEnvironmentBean().getIsolationLevel(); }