/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.sql; import com.caucho.sql.spy.SpyConnection; import com.caucho.sql.spy.SpyXAResource; import com.caucho.util.Alarm; import com.caucho.util.L10N; import com.caucho.util.LruCache; import javax.resource.NotSupportedException; import javax.resource.ResourceException; import javax.resource.spi.ConnectionEvent; import javax.resource.spi.ConnectionEventListener; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.LocalTransaction; import javax.resource.spi.ManagedConnection; import javax.resource.spi.ManagedConnectionMetaData; import javax.security.auth.Subject; import javax.sql.PooledConnection; import javax.sql.XAConnection; import javax.transaction.xa.XAResource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; /** * Represents a single pooled connection. For the most part, it just * passes the requests to the underlying JDBC connection. * * <p>Closing the connection will return the real connection to the pool * and close any statements. */ public class ManagedConnectionImpl implements ManagedConnection, javax.sql.ConnectionEventListener { protected static final Logger log = Logger.getLogger(ManagedConnectionImpl.class.getName()); protected static L10N L = new L10N(ManagedConnectionImpl.class); // Identifier for spy, etc. private final String _id; private final ManagedFactoryImpl _factory; private final DBPoolImpl _dbPool; private final DriverConfig _driver; private final ConnectionConfig _connConfig; private final Credential _credentials; private PooledConnection _pooledConnection; private Connection _driverConnection; private XAResource _xaResource; private LocalTransaction _localTransaction; private ConnectionEventListener _listener; private ConnectionEvent _connClosedEvent; private ResourceException _connException; private long _lastEventTime; // Cached prepared statements. private LruCache<PreparedStatementKey,PreparedStatementCacheItem> _preparedStatementCache; // Static prepared statement key. private PreparedStatementKey _key; // Current value for isolation private int _isolation = -1; // Value for getAutoCommit private boolean _autoCommit = true; // old value for getReadOnly private boolean _readOnly = false; private boolean _hasCatalog; // old value for getCatalog private String _catalogOrig = null; // current value for getCatalog private String _catalog = null; // old value for isolation private int _oldIsolation = -1; // old value for getTypeMap private Map<String,Class<?>> _typeMap; // returns true if a ping is required private boolean _isPingRequired; ManagedConnectionImpl(ManagedFactoryImpl factory, DriverConfig driver, ConnectionConfig connConfig, Credential credentials) throws SQLException { _factory = factory; _dbPool = factory.getDBPool(); _id = _dbPool.newSpyId(driver); _driver = driver; _connConfig = connConfig; _credentials = credentials; _connClosedEvent = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED); initDriverConnection(); _lastEventTime = Alarm.getCurrentTime(); int preparedStatementCacheSize = _dbPool.getPreparedStatementCacheSize(); if (preparedStatementCacheSize > 0) { _preparedStatementCache = new LruCache<PreparedStatementKey,PreparedStatementCacheItem>(preparedStatementCacheSize); _key = new PreparedStatementKey(); } } /** * Returns the db pool. */ DBPoolImpl getDBPool() { return _dbPool; } /** * Returns the credentials. */ Credential getCredentials() { return _credentials; } public boolean isClosed() { try { if (_driverConnection != null) return _driverConnection.isClosed(); else return false; } catch (SQLException e) { log.log(Level.FINEST, e.toString(), e); return true; } } /** * Returns true if statements should be wrapped. */ boolean isWrapStatements() { return _dbPool.isWrapStatements(); } /** * Returns the underlying connection. */ @Override public Object getConnection(Subject subject, ConnectionRequestInfo info) throws ResourceException { if (_connException != null) throw _connException; if (! ping()) return null; _lastEventTime = Alarm.getCurrentTime(); return new UserConnection(this); } /** * Associates the associate connection. */ @Override public void associateConnection(Object connection) throws ResourceException { _lastEventTime = Alarm.getCurrentTime(); UserConnection uConn = (UserConnection) connection; uConn.associate(this); } /** * Adds a connection event listener. */ @Override public void addConnectionEventListener(ConnectionEventListener listener) { if (_listener != null && _listener != listener) throw new IllegalStateException(); _listener = listener; } /** * Removes a connection event listener. */ @Override public void removeConnectionEventListener(ConnectionEventListener listener) { if (_listener == listener) _listener = null; } /** * Returns the driver connection. */ private void initDriverConnection() throws SQLException { if (_driverConnection != null) throw new IllegalStateException(); String user = _driver.getUser(); String password = _driver.getPassword(); if (_credentials != null) { user = _credentials.getUserName(); password = _credentials.getPassword(); } _pooledConnection = _driver.createPooledConnection(user, password); if (_pooledConnection != null) { _pooledConnection.addConnectionEventListener(this); _driverConnection = _pooledConnection.getConnection(); } if (_driverConnection == null) _driverConnection = _driver.createDriverConnection(user, password); if (_driverConnection == null) throw new SQLException(L.l("Failed to create driver connection for {0}.", _driver)); DBPoolImpl dbPool = getDBPool(); long transactionTimeout = dbPool.getTransactionTimeout(); if (dbPool.isXA() && ! _connConfig.isReadOnly()) { if (_pooledConnection instanceof XAConnection) { try { _xaResource = ((XAConnection) _pooledConnection).getXAResource(); } catch (SQLException e) { log.log(Level.FINE, e.toString(), e); } } if (_xaResource != null && dbPool.isXAForbidSameRM()) _xaResource = new DisjointXAResource(_xaResource); if (transactionTimeout > 0 && _xaResource != null) { try { _xaResource.setTransactionTimeout((int) (transactionTimeout / 1000)); } catch (Throwable e) { log.log(Level.FINER, e.toString(), e); } } boolean allowLocalTransaction = true; String className = ""; if (_pooledConnection != null) className = _pooledConnection.getClass().getName(); if (! (_pooledConnection instanceof XAConnection)) { } else if (className.startsWith("oracle")) { // Oracle does not allow local transactions allowLocalTransaction = false; } else if (className.equals("com.mysql.jdbc.jdbc2.optional.MysqlXAConnection")) { allowLocalTransaction = false; } if (allowLocalTransaction) _localTransaction = new LocalTransactionImpl(); } if (dbPool.isSpy()) { _driverConnection = new SpyConnection(_driverConnection, _dbPool.getSpyDataSource(), _id); if (_xaResource != null) _xaResource = new SpyXAResource(_id, _xaResource); } int isolation = _connConfig.getTransactionIsolation(); if (isolation >= 0) _driverConnection.setTransactionIsolation(isolation); if (_connConfig.isReadOnly()) _driverConnection.setReadOnly(true); String configCatalog = _connConfig.getCatalog(); if (configCatalog != null) { _hasCatalog = true; _catalogOrig = configCatalog; _driverConnection.setCatalog(_catalogOrig); } } /** * Returns the driver connection. */ Connection getDriverConnection() { return _driverConnection; } /* * Returns the driver. */ public Class<?> getDriverClass() { return _driver.getDriverClass(); } /* * Returns the driver URL. */ public String getURL() { return getDBPool().getURL(); } /** * Returns the XA resource for the connection. */ @Override public XAResource getXAResource() throws ResourceException { if (_xaResource != null) return _xaResource; throw new NotSupportedException(); } /** * Returns the XA resource for the connection. */ @Override public LocalTransaction getLocalTransaction() throws ResourceException { return _localTransaction; } /** * Returns the meta data. */ @Override public ManagedConnectionMetaData getMetaData() throws ResourceException { throw new NotSupportedException(); } /** * Sets the log writer. */ @Override public void setLogWriter(PrintWriter out) throws ResourceException { } /** * Gets the log writer. */ @Override public PrintWriter getLogWriter() throws ResourceException { return null; } /** * Sets the isolation. */ public void setIsolation(int isolation) throws SQLException { } /** * Returns a new or cached prepared statement. */ PreparedStatement prepareStatement(UserConnection uConn, String sql, int resultType) throws SQLException { Connection conn = getDriverConnection(); if (conn == null) throw new IllegalStateException(L.l("can't prepare statement from closed connection")); if (resultType > 0) return conn.prepareStatement(sql, resultType); else return conn.prepareStatement(sql); } /** * Returns a new or cached prepared statement. */ PreparedStatement prepareStatement(UserConnection uConn, String sql) throws SQLException { PreparedStatementKey key = _key; Connection conn = getDriverConnection(); if (conn == null) throw new IllegalStateException(L.l("can't prepare statement from closed connection")); if (key == null) { return conn.prepareStatement(sql); } boolean hasItem = false; synchronized (key) { key.init(sql); PreparedStatementCacheItem item = _preparedStatementCache.get(key); if (item != null) { UserPreparedStatement upStmt = item.toActive(uConn); if (upStmt != null) return upStmt; hasItem = ! item.isRemoved(); } } PreparedStatement pStmt; pStmt = conn.prepareStatement(sql); if (hasItem) return pStmt; key = new PreparedStatementKey(sql); PreparedStatementCacheItem item; item = new PreparedStatementCacheItem(key, pStmt, this); UserPreparedStatement upStmt = item.toActive(uConn); if (upStmt == null) throw new IllegalStateException("preparedStatement can't activate"); _preparedStatementCache.put(key, item); return upStmt; } /** * Removes a cached item. */ void remove(PreparedStatementKey key) { _preparedStatementCache.remove(key); } @Override public void connectionClosed(javax.sql.ConnectionEvent event) { sendFatalEvent(new SQLException(L.l("unexpected close event from pool"))); closeEvent(null); } @Override public void connectionErrorOccurred(javax.sql.ConnectionEvent event) { sendFatalEvent(event.getSQLException()); } /** * Sends the close event. */ public void closeEvent(UserConnection userConn) { if (_listener != null) { if (_connException != null) { sendFatalEvent(_connException); } ConnectionEvent evt; synchronized (this) { evt = _connClosedEvent; _connClosedEvent = null; } if (evt == null) evt = new ConnectionEvent(this, ConnectionEvent.CONNECTION_CLOSED); evt.setConnectionHandle(userConn); _listener.connectionClosed(evt); evt.setConnectionHandle(null); _connClosedEvent = evt; _lastEventTime = Alarm.getCurrentTime(); } } /** * Sends the fatal event. */ public void fatalEvent() { fatalEvent(new ResourceException("fatal event")); } /** * Sends the fatal event. */ public void fatalEvent(Exception e) { if (_pooledConnection != null) { } else if (e instanceof ResourceException) _connException = (ResourceException) e; else _connException = new ResourceException(e); } /** * Sends the fatal event. */ public void sendFatalEvent(Exception e) { _isPingRequired = true; if (_listener != null) { ConnectionEvent event; event = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED, e); _listener.connectionErrorOccurred(event); } } public void onSqlException(SQLException e) { setPingRequired(); } public void onRuntimeException(RuntimeException e) { setPingRequired(); } public void setPingRequired() { _isPingRequired = true; } /** * When closed, the item is not put into the idle pool. */ void killPool() { if (_listener != null) { ConnectionEvent event; event = new ConnectionEvent(this, ConnectionEvent.CONNECTION_ERROR_OCCURRED); _listener.connectionErrorOccurred(event); } } /** * Sets the auto-commit. */ public void setAutoCommit(boolean autoCommit) throws SQLException { try { _autoCommit = autoCommit; _driverConnection.setAutoCommit(autoCommit); } catch (SQLException e) { fatalEvent(); throw e; } } /** * Sets the read only attribute. */ public void setReadOnly(boolean readOnly) throws SQLException { try { _readOnly = readOnly; _driverConnection.setReadOnly(readOnly); } catch (SQLException e) { fatalEvent(); throw e; } } /** * Sets the JDBC catalog. */ public void setCatalog(String catalog) throws SQLException { try { if (! _hasCatalog) { _hasCatalog = true; _catalogOrig = _driverConnection.getCatalog(); _catalog = _catalogOrig; } if (catalog == null || catalog.length() == 0) { // Clear the current catalog name but don't invoke setCatalog() // on the driver. _catalog = null; } else if (_catalog != null && _catalog.equals(catalog)) { // No-op when setting to the currently selected catalog name } else { _driverConnection.setCatalog(catalog); _catalog = catalog; } } catch (SQLException e) { fatalEvent(); throw e; } } /** * Sets the connection's type map. */ public void setTypeMap(Map<String,Class<?>> map) throws SQLException { try { if (_typeMap == null) _typeMap = _driverConnection.getTypeMap(); _driverConnection.setTypeMap(map); } catch (SQLException e) { throw e; } } /** * Sets the connection's isolation. */ public void setTransactionIsolation(int isolation) throws SQLException { try { _oldIsolation = _driverConnection.getTransactionIsolation(); _isolation = isolation; _driverConnection.setTransactionIsolation(isolation); } catch (SQLException e) { throw e; } } /** * Cleans up the instance. */ @Override public void cleanup() throws ResourceException { Connection conn = _driverConnection; if (conn == null) return; try { /* // If there's a pooled connection, it can cleanup itself if (_pooledConnection != null) { _autoCommit = true; _driverConnection = _pooledConnection.getConnection(); _isolation = _oldIsolation = -1; return; } */ _lastEventTime = Alarm.getCurrentTime(); if (_readOnly) conn.setReadOnly(false); _readOnly = false; if (_catalog != null && ! _catalog.equals(_catalogOrig) && _catalogOrig != null && ! "".equals(_catalogOrig)) { conn.setCatalog(_catalogOrig); } _catalog = null; if (_typeMap != null) conn.setTypeMap(_typeMap); _typeMap = null; // Oracle requires a rollback after a reset of // the transaction isolation, since setting the isolation // starts a new transaction boolean needsRollback = ! _autoCommit; if (_isolation != _oldIsolation) { needsRollback = true; conn.setTransactionIsolation(_oldIsolation); } _isolation = _oldIsolation; // #4663 /* if (needsRollback) conn.rollback(); */ if (! _autoCommit) { conn.setAutoCommit(true); } _autoCommit = true; conn.clearWarnings(); } catch (SQLException e) { throw new ResourceException(e); } } /** * Returns true if the connection is valid. */ boolean isValid() { try { return ping(); } catch (Exception e) { log.log(Level.FINE, e.toString(), e); return false; } } /** * Checks the validity with ping. */ private boolean ping() throws ResourceException { DBPoolImpl dbPool = _factory.getDBPool(); long now = Alarm.getCurrentTime(); long pingInterval = dbPool.getPingInterval(); boolean isPingRequired = _isPingRequired; _isPingRequired = false; Connection conn = _driverConnection; if (isClosed()) { return false; } if (isPingRequired) { } else if (pingInterval > 0 && now < _lastEventTime + pingInterval) { return true; } else if (now < _lastEventTime + 1000) { return true; } try { _lastEventTime = now; if (conn == null) { return false; } String pingQuery = dbPool.getPingQuery(); if (conn.isClosed()) { return false; } if (! dbPool.isPing() || pingQuery == null) { return ! isPingRequired; } Statement stmt = conn.createStatement(); try { ResultSet rs = stmt.executeQuery(pingQuery); rs.next(); rs.close(); return true; } finally { stmt.close(); } } catch (SQLException e) { throw new ResourceException(e); } } /** * Destroys the physical connection. */ @Override public void destroy() throws ResourceException { log.finer("destroy " + this); PooledConnection poolConn = _pooledConnection; _pooledConnection = null; Connection driverConn = _driverConnection; _driverConnection = null; if (_preparedStatementCache != null) { Iterator<PreparedStatementCacheItem> iter; iter = _preparedStatementCache.values(); while (iter.hasNext()) { PreparedStatementCacheItem item = iter.next(); item.destroy(); } } try { if (poolConn != null) { poolConn.close(); // env/11m0 // driverConn = null; } } catch (SQLException e) { throw new ResourceException(e); } try { if (driverConn != null) driverConn.close(); } catch (SQLException e) { log.log(Level.WARNING, e.toString(), e); } } @Override public String toString() { return getClass().getSimpleName() + "[" + _id + "]"; } class LocalTransactionImpl implements LocalTransaction { private boolean _oldAutoCommit; @Override public void begin() throws ResourceException { try { _oldAutoCommit = _autoCommit; setAutoCommit(false); } catch (SQLException e) { throw new ResourceException(e) ; } } @Override public void commit() throws ResourceException { Connection conn = _driverConnection; if (conn == null) throw new ResourceException(L.l("connection is closed")); try { conn.commit(); } catch (SQLException e) { throw new ResourceException(e) ; } try { setAutoCommit(_oldAutoCommit); } catch (SQLException e) { throw new ResourceException(e) ; } } @Override public void rollback() throws ResourceException { Connection conn = _driverConnection; if (conn == null) throw new ResourceException(L.l("connection is closed")); try { conn.rollback(); } catch (SQLException e) { throw new ResourceException(e) ; } try { setAutoCommit(_oldAutoCommit); } catch (SQLException e) { throw new ResourceException(e) ; } } } }