001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.sql;
031:
032: import com.caucho.log.Log;
033: import com.caucho.sql.spy.SpyConnection;
034: import com.caucho.sql.spy.SpyXAResource;
035: import com.caucho.util.Alarm;
036: import com.caucho.util.L10N;
037: import com.caucho.util.LruCache;
038:
039: import javax.resource.NotSupportedException;
040: import javax.resource.ResourceException;
041: import javax.resource.spi.ConnectionEvent;
042: import javax.resource.spi.ConnectionEventListener;
043: import javax.resource.spi.ConnectionRequestInfo;
044: import javax.resource.spi.LocalTransaction;
045: import javax.resource.spi.ManagedConnection;
046: import javax.resource.spi.ManagedConnectionMetaData;
047: import javax.security.auth.Subject;
048: import javax.sql.PooledConnection;
049: import javax.sql.XAConnection;
050: import javax.transaction.xa.XAResource;
051: import java.io.PrintWriter;
052: import java.sql.Connection;
053: import java.sql.PreparedStatement;
054: import java.sql.ResultSet;
055: import java.sql.SQLException;
056: import java.sql.Statement;
057: import java.util.Iterator;
058: import java.util.Map;
059: import java.util.logging.Level;
060: import java.util.logging.Logger;
061:
062: /**
063: * Represents a single pooled connection. For the most part, it just
064: * passes the requests to the underlying JDBC connection.
065: *
066: * <p>Closing the connection will return the real connection to the pool
067: * and close any statements.
068: */
069: public class ManagedConnectionImpl implements ManagedConnection,
070: javax.sql.ConnectionEventListener {
071: protected static final Logger log = Log
072: .open(ManagedConnectionImpl.class);
073: protected static L10N L = new L10N(ManagedConnectionImpl.class);
074:
075: // Identifier for spy, etc.
076: private final String _id;
077:
078: private final ManagedFactoryImpl _factory;
079: private final DBPoolImpl _dbPool;
080:
081: private final DriverConfig _driver;
082: private final ConnectionConfig _connConfig;
083:
084: private final Credential _credentials;
085:
086: private PooledConnection _pooledConnection;
087: private Connection _driverConnection;
088:
089: private XAResource _xaResource;
090: private LocalTransaction _localTransaction;
091:
092: private ConnectionEventListener _listener;
093: private ConnectionEvent _connClosedEvent;
094:
095: private ResourceException _connException;
096:
097: private long _lastEventTime;
098:
099: // Cached prepared statements.
100: private LruCache<PreparedStatementKey, PreparedStatementCacheItem> _preparedStatementCache;
101:
102: // Static prepared statement key.
103: private PreparedStatementKey _key;
104:
105: // Current value for isolation
106: private int _isolation = -1;
107:
108: // Value for getAutoCommit
109: private boolean _autoCommit = true;
110: // old value for getReadOnly
111: private boolean _readOnly = false;
112:
113: private boolean _hasCatalog;
114: // old value for getCatalog
115: private String _catalogOrig = null;
116: // current value for getCatalog
117: private String _catalog = null;
118:
119: // old value for isolation
120: private int _oldIsolation = -1;
121: // old value for getTypeMap
122: private Map _typeMap;
123:
124: ManagedConnectionImpl(ManagedFactoryImpl factory,
125: DriverConfig driver, ConnectionConfig connConfig,
126: Credential credentials) throws SQLException {
127: _factory = factory;
128: _dbPool = factory.getDBPool();
129: _id = _dbPool.newSpyId();
130:
131: _driver = driver;
132: _connConfig = connConfig;
133: _credentials = credentials;
134:
135: _connClosedEvent = new ConnectionEvent(this ,
136: ConnectionEvent.CONNECTION_CLOSED);
137:
138: initDriverConnection();
139:
140: _lastEventTime = Alarm.getCurrentTime();
141:
142: int preparedStatementCacheSize = _dbPool
143: .getPreparedStatementCacheSize();
144:
145: if (preparedStatementCacheSize > 0) {
146: _preparedStatementCache = new LruCache<PreparedStatementKey, PreparedStatementCacheItem>(
147: preparedStatementCacheSize);
148: _key = new PreparedStatementKey();
149: }
150: }
151:
152: /**
153: * Returns the db pool.
154: */
155: DBPoolImpl getDBPool() {
156: return _dbPool;
157: }
158:
159: /**
160: * Returns the credentials.
161: */
162: Credential getCredentials() {
163: return _credentials;
164: }
165:
166: /**
167: * Returns true if statements should be wrapped.
168: */
169: boolean isWrapStatements() {
170: return _dbPool.isWrapStatements();
171: }
172:
173: /**
174: * Returns the underlying connection.
175: */
176: public Object getConnection(Subject subject,
177: ConnectionRequestInfo info) throws ResourceException {
178: if (_connException != null)
179: throw _connException;
180:
181: if (!ping())
182: return null;
183:
184: _lastEventTime = Alarm.getCurrentTime();
185:
186: return new UserConnection(this );
187: }
188:
189: /**
190: * Associates the associate connection.
191: */
192: public void associateConnection(Object connection)
193: throws ResourceException {
194: _lastEventTime = Alarm.getCurrentTime();
195:
196: UserConnection uConn = (UserConnection) connection;
197:
198: uConn.associate(this );
199: }
200:
201: /**
202: * Adds a connection event listener.
203: */
204: public void addConnectionEventListener(
205: ConnectionEventListener listener) {
206: if (_listener != null && _listener != listener)
207: throw new IllegalStateException();
208:
209: _listener = listener;
210: }
211:
212: /**
213: * Removes a connection event listener.
214: */
215: public void removeConnectionEventListener(
216: ConnectionEventListener listener) {
217: if (_listener == listener)
218: _listener = null;
219: }
220:
221: /**
222: * Returns the driver connection.
223: */
224: private void initDriverConnection() throws SQLException {
225: if (_driverConnection != null)
226: throw new IllegalStateException();
227:
228: String user = _driver.getUser();
229: String password = _driver.getPassword();
230:
231: if (_credentials != null) {
232: user = _credentials.getUserName();
233: password = _credentials.getPassword();
234: }
235:
236: _pooledConnection = _driver.createPooledConnection(user,
237: password);
238:
239: if (_pooledConnection != null) {
240: _pooledConnection.addConnectionEventListener(this );
241:
242: _driverConnection = _pooledConnection.getConnection();
243: }
244:
245: if (_driverConnection == null)
246: _driverConnection = _driver.createDriverConnection(user,
247: password);
248:
249: if (_driverConnection == null)
250: throw new SQLException(L
251: .l("Failed to create driver connection."));
252:
253: DBPoolImpl dbPool = getDBPool();
254:
255: long transactionTimeout = dbPool.getTransactionTimeout();
256: if (dbPool.isXA() && !_connConfig.isReadOnly()) {
257: if (_pooledConnection instanceof XAConnection) {
258: try {
259: _xaResource = ((XAConnection) _pooledConnection)
260: .getXAResource();
261: } catch (SQLException e) {
262: log.log(Level.FINE, e.toString(), e);
263: }
264: }
265:
266: if (_xaResource != null && dbPool.isXAForbidSameRM())
267: _xaResource = new DisjointXAResource(_xaResource);
268:
269: if (transactionTimeout > 0 && _xaResource != null) {
270: try {
271: _xaResource
272: .setTransactionTimeout((int) (transactionTimeout / 1000));
273: } catch (Throwable e) {
274: log.log(Level.FINER, e.toString(), e);
275: }
276: }
277:
278: boolean allowLocalTransaction = true;
279: String className = "";
280:
281: if (_pooledConnection != null)
282: className = _pooledConnection.getClass().getName();
283:
284: if (!(_pooledConnection instanceof XAConnection)) {
285: } else if (className.startsWith("oracle")) {
286: // Oracle does not allow local transactions
287: allowLocalTransaction = false;
288: } else if (className
289: .equals("com.mysql.jdbc.jdbc2.optional.MysqlXAConnection")) {
290: allowLocalTransaction = false;
291: }
292:
293: if (allowLocalTransaction)
294: _localTransaction = new LocalTransactionImpl();
295: }
296:
297: if (dbPool.isSpy()) {
298: _driverConnection = new SpyConnection(_driverConnection,
299: _dbPool.getSpyDataSource(), _id);
300:
301: if (_xaResource != null)
302: _xaResource = new SpyXAResource(_id, _xaResource);
303: }
304:
305: int isolation = _connConfig.getTransactionIsolation();
306: if (isolation >= 0)
307: _driverConnection.setTransactionIsolation(isolation);
308:
309: if (_connConfig.isReadOnly())
310: _driverConnection.setReadOnly(true);
311:
312: String configCatalog = _connConfig.getCatalog();
313: if (configCatalog != null) {
314: _hasCatalog = true;
315: _catalogOrig = configCatalog;
316: _driverConnection.setCatalog(_catalogOrig);
317: }
318: }
319:
320: /**
321: * Returns the driver connection.
322: */
323: Connection getDriverConnection() {
324: return _driverConnection;
325: }
326:
327: /*
328: * Returns the driver.
329: */
330: public Class getDriverClass() {
331: return _driver.getDriverClass();
332: }
333:
334: /*
335: * Returns the driver URL.
336: */
337: public String getURL() {
338: return getDBPool().getURL();
339: }
340:
341: /**
342: * Returns the XA resource for the connection.
343: */
344: public XAResource getXAResource() throws ResourceException {
345: if (_xaResource != null)
346: return _xaResource;
347:
348: throw new NotSupportedException();
349: }
350:
351: /**
352: * Returns the XA resource for the connection.
353: */
354: public LocalTransaction getLocalTransaction()
355: throws ResourceException {
356: return _localTransaction;
357: }
358:
359: /**
360: * Returns the meta data.
361: */
362: public ManagedConnectionMetaData getMetaData()
363: throws ResourceException {
364: throw new NotSupportedException();
365: }
366:
367: /**
368: * Sets the log writer.
369: */
370: public void setLogWriter(PrintWriter out) throws ResourceException {
371: }
372:
373: /**
374: * Gets the log writer.
375: */
376: public PrintWriter getLogWriter() throws ResourceException {
377: return null;
378: }
379:
380: /**
381: * Sets the isolation.
382: */
383: public void setIsolation(int isolation) throws SQLException {
384: }
385:
386: /**
387: * Returns a new or cached prepared statement.
388: */
389: PreparedStatement prepareStatement(UserConnection uConn,
390: String sql, int resultType) throws SQLException {
391: Connection conn = getDriverConnection();
392:
393: if (conn == null)
394: throw new IllegalStateException(
395: L
396: .l("can't prepare statement from closed connection"));
397:
398: if (resultType > 0)
399: return conn.prepareStatement(sql, resultType);
400: else
401: return conn.prepareStatement(sql);
402: }
403:
404: /**
405: * Returns a new or cached prepared statement.
406: */
407: PreparedStatement prepareStatement(UserConnection uConn, String sql)
408: throws SQLException {
409: PreparedStatementKey key = _key;
410:
411: Connection conn = getDriverConnection();
412:
413: if (conn == null)
414: throw new IllegalStateException(
415: L
416: .l("can't prepare statement from closed connection"));
417:
418: if (key == null) {
419: return conn.prepareStatement(sql);
420: }
421:
422: boolean hasItem = false;
423:
424: synchronized (key) {
425: key.init(sql);
426:
427: PreparedStatementCacheItem item = _preparedStatementCache
428: .get(key);
429:
430: if (item != null) {
431: UserPreparedStatement upStmt = item.toActive(uConn);
432:
433: if (upStmt != null)
434: return upStmt;
435:
436: hasItem = !item.isRemoved();
437: }
438: }
439:
440: PreparedStatement pStmt;
441: pStmt = conn.prepareStatement(sql);
442:
443: if (hasItem)
444: return pStmt;
445:
446: key = new PreparedStatementKey(sql);
447:
448: PreparedStatementCacheItem item;
449: item = new PreparedStatementCacheItem(key, pStmt, this );
450:
451: UserPreparedStatement upStmt = item.toActive(uConn);
452:
453: if (upStmt == null)
454: throw new IllegalStateException(
455: "preparedStatement can't activate");
456:
457: _preparedStatementCache.put(key, item);
458:
459: return upStmt;
460: }
461:
462: /**
463: * Removes a cached item.
464: */
465: void remove(PreparedStatementKey key) {
466: _preparedStatementCache.remove(key);
467: }
468:
469: public void connectionClosed(javax.sql.ConnectionEvent event) {
470: sendFatalEvent(new SQLException(L
471: .l("unexpected close event from pool")));
472: closeEvent(null);
473: }
474:
475: public void connectionErrorOccurred(javax.sql.ConnectionEvent event) {
476: sendFatalEvent(event.getSQLException());
477: }
478:
479: /**
480: * Sends the close event.
481: */
482: public void closeEvent(UserConnection userConn) {
483: if (_listener != null) {
484: if (_connException != null) {
485: sendFatalEvent(_connException);
486: }
487:
488: ConnectionEvent evt;
489: synchronized (this ) {
490: evt = _connClosedEvent;
491: _connClosedEvent = null;
492: }
493:
494: if (evt == null)
495: evt = new ConnectionEvent(this ,
496: ConnectionEvent.CONNECTION_CLOSED);
497:
498: evt.setConnectionHandle(userConn);
499:
500: _listener.connectionClosed(evt);
501:
502: evt.setConnectionHandle(null);
503:
504: _connClosedEvent = evt;
505:
506: _lastEventTime = Alarm.getCurrentTime();
507: }
508: }
509:
510: /**
511: * Sends the fatal event.
512: */
513: public void fatalEvent() {
514: fatalEvent(new ResourceException("fatal event"));
515: }
516:
517: /**
518: * Sends the fatal event.
519: */
520: public void fatalEvent(Exception e) {
521: if (_pooledConnection != null) {
522: } else if (e instanceof ResourceException)
523: _connException = (ResourceException) e;
524: else
525: _connException = new ResourceException(e);
526: }
527:
528: /**
529: * Sends the fatal event.
530: */
531: public void sendFatalEvent(Exception e) {
532: if (_listener != null) {
533: ConnectionEvent event;
534:
535: event = new ConnectionEvent(this ,
536: ConnectionEvent.CONNECTION_ERROR_OCCURRED, e);
537:
538: _listener.connectionErrorOccurred(event);
539: }
540: }
541:
542: /**
543: * When closed, the item is not put into the idle pool.
544: */
545: void killPool() {
546: if (_listener != null) {
547: ConnectionEvent event;
548:
549: event = new ConnectionEvent(this ,
550: ConnectionEvent.CONNECTION_ERROR_OCCURRED);
551:
552: _listener.connectionErrorOccurred(event);
553: }
554: }
555:
556: /**
557: * Sets the auto-commit.
558: */
559: public void setAutoCommit(boolean autoCommit) throws SQLException {
560: try {
561: _autoCommit = autoCommit;
562: _driverConnection.setAutoCommit(autoCommit);
563: } catch (SQLException e) {
564: fatalEvent();
565: throw e;
566: }
567: }
568:
569: /**
570: * Sets the read only attribute.
571: */
572: public void setReadOnly(boolean readOnly) throws SQLException {
573: try {
574: _readOnly = readOnly;
575: _driverConnection.setReadOnly(readOnly);
576: } catch (SQLException e) {
577: fatalEvent();
578: throw e;
579: }
580: }
581:
582: /**
583: * Sets the JDBC catalog.
584: */
585: public void setCatalog(String catalog) throws SQLException {
586: try {
587: if (!_hasCatalog) {
588: _hasCatalog = true;
589: _catalogOrig = _driverConnection.getCatalog();
590: _catalog = _catalogOrig;
591: }
592:
593: if (catalog == null || catalog.length() == 0) {
594: // Clear the current catalog name but don't invoke setCatalog()
595: // on the driver.
596:
597: _catalog = null;
598: } else if (_catalog != null && _catalog.equals(catalog)) {
599: // No-op when setting to the currently selected catalog name
600: } else {
601: _driverConnection.setCatalog(catalog);
602: _catalog = catalog;
603: }
604: } catch (SQLException e) {
605: fatalEvent();
606: throw e;
607: }
608: }
609:
610: /**
611: * Sets the connection's type map.
612: */
613: public void setTypeMap(Map map) throws SQLException {
614: try {
615: if (_typeMap == null)
616: _typeMap = _driverConnection.getTypeMap();
617:
618: _driverConnection.setTypeMap(map);
619: } catch (SQLException e) {
620: throw e;
621: }
622: }
623:
624: /**
625: * Sets the connection's isolation.
626: */
627: public void setTransactionIsolation(int isolation)
628: throws SQLException {
629: try {
630: _oldIsolation = _driverConnection.getTransactionIsolation();
631: _isolation = isolation;
632:
633: _driverConnection.setTransactionIsolation(isolation);
634: } catch (SQLException e) {
635: throw e;
636: }
637: }
638:
639: /**
640: * Cleans up the instance.
641: */
642: public void cleanup() throws ResourceException {
643: Connection conn = _driverConnection;
644:
645: if (conn == null)
646: return;
647:
648: try {
649: /*
650: // If there's a pooled connection, it can cleanup itself
651: if (_pooledConnection != null) {
652: _autoCommit = true;
653: _driverConnection = _pooledConnection.getConnection();
654: _isolation = _oldIsolation = -1;
655: return;
656: }
657: */
658:
659: if (_readOnly)
660: conn.setReadOnly(false);
661: _readOnly = false;
662:
663: if (_catalog != null && !_catalog.equals(_catalogOrig)
664: && _catalogOrig != null && !"".equals(_catalogOrig)) {
665: conn.setCatalog(_catalogOrig);
666: }
667: _catalog = null;
668:
669: if (_typeMap != null)
670: conn.setTypeMap(_typeMap);
671: _typeMap = null;
672:
673: // Oracle requires a rollback after a reset of
674: // the transaction isolation, since setting the isolation
675: // starts a new transaction
676: boolean needsRollback = !_autoCommit;
677: if (_isolation != _oldIsolation) {
678: needsRollback = true;
679: conn.setTransactionIsolation(_oldIsolation);
680: }
681: _isolation = _oldIsolation;
682:
683: if (needsRollback)
684: conn.rollback();
685:
686: if (!_autoCommit) {
687: conn.setAutoCommit(true);
688: }
689: _autoCommit = true;
690:
691: conn.clearWarnings();
692: } catch (SQLException e) {
693: throw new ResourceException(e);
694: }
695: }
696:
697: /**
698: * Returns true if the connection is valid.
699: */
700: boolean isValid() {
701: try {
702: return ping();
703: } catch (Throwable e) {
704: log.log(Level.FINE, e.toString(), e);
705:
706: return false;
707: }
708: }
709:
710: /**
711: * Checks the validity with ping.
712: */
713: private boolean ping() throws ResourceException {
714: DBPoolImpl dbPool = _factory.getDBPool();
715:
716: long now = Alarm.getCurrentTime();
717:
718: if (now < _lastEventTime + 1000)
719: return true;
720:
721: if (!dbPool.isPing())
722: return true;
723:
724: long pingInterval = dbPool.getPingInterval();
725: if (pingInterval > 0 && now < _lastEventTime + pingInterval)
726: return true;
727:
728: Connection conn = _driverConnection;
729:
730: try {
731: if (conn == null || conn.isClosed()) {
732: return false;
733: }
734:
735: String pingQuery = dbPool.getPingQuery();
736:
737: if (pingQuery == null)
738: return true;
739:
740: Statement stmt = conn.createStatement();
741:
742: try {
743: ResultSet rs = stmt.executeQuery(pingQuery);
744: rs.close();
745:
746: return true;
747: } finally {
748: stmt.close();
749: }
750: } catch (SQLException e) {
751: throw new ResourceException(e);
752: }
753: }
754:
755: /**
756: * Destroys the physical connection.
757: */
758: public void destroy() throws ResourceException {
759: log.finer("destroy " + this );
760:
761: PooledConnection poolConn = _pooledConnection;
762: _pooledConnection = null;
763:
764: Connection driverConn = _driverConnection;
765: _driverConnection = null;
766:
767: if (_preparedStatementCache != null) {
768: Iterator<PreparedStatementCacheItem> iter;
769:
770: iter = _preparedStatementCache.values();
771:
772: while (iter.hasNext()) {
773: PreparedStatementCacheItem item = iter.next();
774:
775: item.destroy();
776: }
777: }
778:
779: try {
780: if (poolConn != null) {
781: poolConn.close();
782: driverConn = null;
783: }
784: } catch (SQLException e) {
785: throw new ResourceException(e);
786: }
787:
788: try {
789: if (driverConn != null)
790: driverConn.close();
791: } catch (SQLException e) {
792: log.log(Level.WARNING, e.toString(), e);
793: }
794: }
795:
796: public String toString() {
797: return "ManagedConnectionImpl[" + _id + "]";
798: }
799:
800: class LocalTransactionImpl implements LocalTransaction {
801: private boolean _oldAutoCommit;
802:
803: public void begin() throws ResourceException {
804: try {
805: _oldAutoCommit = _autoCommit;
806:
807: setAutoCommit(false);
808: } catch (SQLException e) {
809: throw new ResourceException(e);
810: }
811: }
812:
813: public void commit() throws ResourceException {
814: Connection conn = _driverConnection;
815:
816: if (conn == null)
817: throw new ResourceException(L.l("connection is closed"));
818:
819: try {
820: conn.commit();
821: } catch (SQLException e) {
822: throw new ResourceException(e);
823: }
824:
825: try {
826: setAutoCommit(_oldAutoCommit);
827: } catch (SQLException e) {
828: throw new ResourceException(e);
829: }
830: }
831:
832: public void rollback() throws ResourceException {
833: Connection conn = _driverConnection;
834:
835: if (conn == null)
836: throw new ResourceException(L.l("connection is closed"));
837:
838: try {
839: conn.rollback();
840: } catch (SQLException e) {
841: throw new ResourceException(e);
842: }
843:
844: try {
845: setAutoCommit(_oldAutoCommit);
846: } catch (SQLException e) {
847: throw new ResourceException(e);
848: }
849: }
850: }
851: }
|