001: /**
002: * EasyBeans
003: * Copyright (C) 2006 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: JManagedConnection.java 1970 2007-10-16 11:49:25Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.component.jdbcpool;
025:
026: import java.sql.Connection;
027: import java.sql.PreparedStatement;
028: import java.sql.ResultSet;
029: import java.sql.SQLException;
030: import java.util.Collections;
031: import java.util.HashMap;
032: import java.util.Iterator;
033: import java.util.Map;
034: import java.util.Vector;
035:
036: import javax.sql.ConnectionEvent;
037: import javax.sql.ConnectionEventListener;
038: import javax.sql.XAConnection;
039: import javax.transaction.Synchronization;
040: import javax.transaction.Transaction;
041: import javax.transaction.xa.XAException;
042: import javax.transaction.xa.XAResource;
043: import javax.transaction.xa.Xid;
044:
045: import org.ow2.util.log.Log;
046: import org.ow2.util.log.LogFactory;
047:
048: /**
049: * This class represents the connection managed by the pool. This connection is
050: * a managed connection and is notified of the transaction events.
051: * @author Philippe Durieux
052: * @author Florent Benoit
053: */
054: public class JManagedConnection implements Comparable, XAConnection,
055: XAResource, Synchronization {
056:
057: /**
058: * Logger.
059: */
060: private static Log logger = LogFactory
061: .getLog(JManagedConnection.class);
062:
063: /**
064: * Connection to the database.
065: */
066: private Connection physicalConnection = null;
067:
068: /**
069: * Connection returned to the user.
070: */
071: private Connection implConn = null;
072:
073: /**
074: * Maximum of prepared statements.
075: */
076: private int pstmtmax = 0;
077:
078: /**
079: * Current number of opened prepared statements.
080: */
081: private int psOpenNb = 0;
082:
083: /**
084: * Event listeners (of PooledConnection).
085: */
086: private Vector<ConnectionEventListener> eventListeners = new Vector<ConnectionEventListener>();
087:
088: /**
089: * count of opening this connection. >0 if open.
090: */
091: private int open = 0;
092:
093: /**
094: * Transaction timeout value.
095: */
096: private int timeout = 0;
097:
098: /**
099: * Transaction the connection is involved with.
100: */
101: private Transaction tx = null;
102:
103: /**
104: * Counter of all managed connections created.
105: */
106: private static int objcount = 0;
107:
108: /**
109: * Identifier of this connection.
110: */
111: private final int identifier;
112:
113: /**
114: * Prepared statements that were reused.
115: */
116: private int reUsedPreparedStatements = 0;
117:
118: /**
119: * List of PreparedStatement in the pool.
120: */
121: private Map<String, JStatement> psList = null;
122:
123: /**
124: * Link to the connection manager.
125: */
126: private ConnectionManager ds = null;
127:
128: /**
129: * Time of the death for this connection.
130: */
131: private long deathTime = 0;
132:
133: /**
134: * Time for closing this connection.
135: */
136: private long closeTime = 0;
137:
138: /**
139: * Builds a new managed connection on a JDBC connection.
140: * @param physicalConnection the physical JDBC Connection.
141: * @param ds the connection manager
142: */
143: public JManagedConnection(final Connection physicalConnection,
144: final ConnectionManager ds) {
145: this .physicalConnection = physicalConnection;
146: this .ds = ds;
147:
148: // An XAConnection holds 2 objects: 1 Connection + 1 XAResource
149: this .implConn = new JConnection(this , physicalConnection);
150:
151: open = 0;
152: deathTime = System.currentTimeMillis() + ds.getMaxAgeMilli();
153:
154: identifier = objcount++;
155:
156: // Prepared statement.
157: pstmtmax = ds.getPstmtMax();
158: psOpenNb = 0;
159: psList = Collections
160: .synchronizedMap(new HashMap<String, JStatement>());
161:
162: }
163:
164: /**
165: * @return The identifier of this JManagedConnection
166: */
167: public int getIdentifier() {
168: return identifier;
169: }
170:
171: /**
172: * Dynamically change the prepared statement pool size.
173: * @param max the maximum of prepared statement.
174: */
175: public void setPstmtMax(final int max) {
176: pstmtmax = max;
177: if (psList == null) {
178: psList = Collections
179: .synchronizedMap(new HashMap<String, JStatement>(
180: pstmtmax));
181: }
182: }
183:
184: /**
185: * Commit the global transaction specified by xid.
186: * @param xid transaction xid
187: * @param onePhase true if one phase commit
188: * @throws XAException XA protocol error
189: */
190: public void commit(final Xid xid, final boolean onePhase)
191: throws XAException {
192: logger.debug("XA-COMMIT for {0}", xid);
193:
194: // Commit the transaction
195: try {
196: physicalConnection.commit();
197: } catch (SQLException e) {
198: logger.error("Cannot commit transaction", e);
199: notifyError(e);
200: throw new XAException("Error on commit");
201: }
202: }
203:
204: /**
205: * Ends the work performed on behalf of a transaction branch.
206: * @param xid transaction xid
207: * @param flags currently unused
208: * @throws XAException XA protocol error
209: */
210: public void end(final Xid xid, final int flags) throws XAException {
211: logger.debug("XA-END for {0}", xid);
212: }
213:
214: /**
215: * Tell the resource manager to forget about a heuristically completed
216: * transaction branch.
217: * @param xid transaction xid
218: * @throws XAException XA protocol error
219: */
220: public void forget(final Xid xid) throws XAException {
221: logger.debug("XA-FORGET for {0}", xid);
222: }
223:
224: /**
225: * Obtain the current transaction timeout value set for this XAResource
226: * instance.
227: * @return the current transaction timeout in seconds
228: * @throws XAException XA protocol error
229: */
230: public int getTransactionTimeout() throws XAException {
231: logger.debug("getTransactionTimeout for {0}", this );
232: return timeout;
233: }
234:
235: /**
236: * Determine if the resource manager instance represented by the target
237: * object is the same as the resource manager instance represented by the
238: * parameter xares.
239: * @param xares An XAResource object
240: * @return True if same RM instance, otherwise false.
241: * @throws XAException XA protocol error
242: */
243: public boolean isSameRM(final XAResource xares) throws XAException {
244:
245: // In this pseudo-driver, we must return true only if
246: // both objects refer to the same XAResource, and not
247: // the same Resource Manager, because actually, we must
248: // send commit/rollback on each XAResource involved in
249: // the transaction.
250: if (xares.equals(this )) {
251: logger.debug("isSameRM = true {0}", this );
252: return true;
253: }
254: logger.debug("isSameRM = false {0}", this );
255: return false;
256: }
257:
258: /**
259: * Ask the resource manager to prepare for a transaction commit of the
260: * transaction specified in xid.
261: * @param xid transaction xid
262: * @throws XAException XA protocol error
263: * @return always OK
264: */
265: public int prepare(final Xid xid) throws XAException {
266: logger.debug("XA-PREPARE for {0}", xid);
267: // No 2PC on standard JDBC drivers
268: return XA_OK;
269: }
270:
271: /**
272: * Obtain a list of prepared transaction branches from a resource manager.
273: * @param flag unused parameter.
274: * @return an array of transaction Xids
275: * @throws XAException XA protocol error
276: */
277: public Xid[] recover(final int flag) throws XAException {
278: logger.debug("XA-RECOVER for {0}", this );
279: // Not implemented
280: return null;
281: }
282:
283: /**
284: * Inform the resource manager to roll back work done on behalf of a
285: * transaction branch.
286: * @param xid transaction xid
287: * @throws XAException XA protocol error
288: */
289: public void rollback(final Xid xid) throws XAException {
290: logger.debug("XA-ROLLBACK for {0}", xid);
291:
292: // Make sure that we are not in AutoCommit mode
293: try {
294: if (physicalConnection.getAutoCommit()) {
295: logger
296: .error("Rollback called on XAResource with AutoCommit set");
297: throw (new XAException(XAException.XA_HEURCOM));
298: }
299: } catch (SQLException e) {
300: logger.error("Cannot getAutoCommit", e);
301: notifyError(e);
302: throw (new XAException("Error on getAutoCommit"));
303: }
304:
305: // Rollback the transaction
306: try {
307: physicalConnection.rollback();
308: } catch (SQLException e) {
309: logger.error("Cannot rollback transaction", e);
310: notifyError(e);
311: throw (new XAException("Error on rollback"));
312: }
313: }
314:
315: /**
316: * Set the current transaction timeout value for this XAResource instance.
317: * @param seconds timeout value, in seconds.
318: * @return always true
319: * @throws XAException XA protocol error
320: */
321: @SuppressWarnings("boxing")
322: public boolean setTransactionTimeout(final int seconds)
323: throws XAException {
324: logger.debug("setTransactionTimeout to {0} for {1}", seconds,
325: this );
326: timeout = seconds;
327: return true;
328: }
329:
330: /**
331: * Start work on behalf of a transaction branch specified in xid.
332: * @param xid transaction xid
333: * @param flags unused parameter
334: * @throws XAException XA protocol error
335: */
336: public void start(final Xid xid, final int flags)
337: throws XAException {
338: logger.debug("XA-START for {0}", xid);
339: }
340:
341: /**
342: * Return an XA resource to the caller.
343: * @return The XAResource
344: * @exception SQLException - if a database-access error occurs
345: */
346: public XAResource getXAResource() throws SQLException {
347: return this ;
348: }
349:
350: /**
351: * Compares this object with another specified object.
352: * @param o the object to compare
353: * @return a value detecting if these objects are matching or not.
354: */
355: public int compareTo(final Object o) {
356: JManagedConnection other = (JManagedConnection) o;
357: int diff = getReUsedPreparedStatements()
358: - other.getReUsedPreparedStatements();
359: if (diff == 0) {
360: return getIdentifier() - other.getIdentifier();
361: }
362: return diff;
363: }
364:
365: /**
366: * @return value of reused prepared statement.
367: */
368: public int getReUsedPreparedStatements() {
369: return reUsedPreparedStatements;
370: }
371:
372: /**
373: * Create an object handle for a database connection.
374: * @exception SQLException - if a database-access error occurs
375: * @return connection used by this managed connection
376: */
377: public Connection getConnection() throws SQLException {
378: // Just return the already created object.
379: return implConn;
380: }
381:
382: /**
383: * Close the database connection.
384: * @exception SQLException - if a database-access error occurs
385: */
386: public void close() throws SQLException {
387:
388: // Close the actual Connection here.
389: if (physicalConnection != null) {
390: physicalConnection.close();
391: } else {
392: logger
393: .error(
394: "Connection already closed. Stack of this new close()",
395: new Exception());
396: }
397: physicalConnection = null;
398: implConn = null;
399: }
400:
401: /**
402: * Add an event listener.
403: * @param listener event listener
404: */
405: public void addConnectionEventListener(
406: final ConnectionEventListener listener) {
407: eventListeners.addElement(listener);
408: }
409:
410: /**
411: * Remove an event listener.
412: * @param listener event listener
413: */
414: public void removeConnectionEventListener(
415: final ConnectionEventListener listener) {
416: eventListeners.removeElement(listener);
417: }
418:
419: /**
420: * synchronization implementation. {@inheritDoc}
421: */
422: public void beforeCompletion() {
423: // nothing to do
424: }
425:
426: /**
427: * synchronization implementation. {@inheritDoc}
428: */
429: public void afterCompletion(final int status) {
430: if (tx != null) {
431: ds.freeConnections(tx);
432: } else {
433: logger.error("NO TX!");
434: }
435: }
436:
437: /**
438: * @return true if connection max age has expired
439: */
440: public boolean isAged() {
441: return (deathTime < System.currentTimeMillis());
442: }
443:
444: /**
445: * @return true if connection is still open
446: */
447: public boolean isOpen() {
448: return (open > 0);
449: }
450:
451: /**
452: * @return open count
453: */
454: public int getOpenCount() {
455: return open;
456: }
457:
458: /**
459: * Check if the connection has been unused for too long time. This occurs
460: * usually when the caller forgot to call close().
461: * @return true if open time has been reached, and not involved in a tx.
462: */
463: public boolean inactive() {
464: return (open > 0 && tx == null && closeTime < System
465: .currentTimeMillis());
466: }
467:
468: /**
469: * @return true if connection is closed
470: */
471: public boolean isClosed() {
472: return (open <= 0);
473: }
474:
475: /**
476: * Notify as opened.
477: */
478: public void hold() {
479: open++;
480: closeTime = System.currentTimeMillis()
481: + ds.getMaxOpenTimeMilli();
482: }
483:
484: /**
485: * notify as closed.
486: * @return true if normal close.
487: */
488: public boolean release() {
489: open--;
490: if (open < 0) {
491: logger.warn("connection was already closed");
492: open = 0;
493: return false;
494: }
495: if (tx == null && open > 0) {
496: logger.error("connection-open counter overflow");
497: open = 0;
498: }
499: return true;
500: }
501:
502: /**
503: * Set the associated transaction.
504: * @param tx Transaction
505: */
506: public void setTx(final Transaction tx) {
507: this .tx = tx;
508: }
509:
510: /**
511: * @return the Transaction
512: */
513: public Transaction getTx() {
514: return tx;
515: }
516:
517: /**
518: * remove this item, ignoring exception on close.
519: */
520: public void remove() {
521: // Close the physical connection
522: try {
523: close();
524: } catch (java.sql.SQLException ign) {
525: logger.error("Could not close Connection: ", ign);
526: }
527:
528: // remove all references (for GC)
529: tx = null;
530:
531: }
532:
533: // -----------------------------------------------------------------
534: // Other methods
535: // -----------------------------------------------------------------
536:
537: /**
538: * Try to find a PreparedStatement in the pool for the given options.
539: * @param sql the sql of the prepared statement
540: * @param resultSetType the type of resultset
541: * @param resultSetConcurrency the concurrency of this resultset
542: * @return a preparestatement object
543: * @throws SQLException if an errors occurs on the database.
544: */
545: public PreparedStatement prepareStatement(final String sql,
546: final int resultSetType, final int resultSetConcurrency)
547: throws SQLException {
548:
549: logger.debug("sql = {0}", sql);
550: if (pstmtmax == 0) {
551: return physicalConnection.prepareStatement(sql,
552: resultSetType, resultSetConcurrency);
553: }
554: JStatement ps = null;
555: synchronized (psList) {
556: ps = psList.get(sql);
557: if (ps != null) {
558: if (!ps.isClosed()) {
559: logger.warn("reuse an open pstmt");
560: }
561: ps.reuse();
562: reUsedPreparedStatements++;
563: } else {
564: // Not found in cache. Create a new one.
565: PreparedStatement aps = physicalConnection
566: .prepareStatement(sql, resultSetType,
567: resultSetConcurrency);
568: ps = new JStatement(aps, this , sql);
569: psList.put(sql, ps);
570: }
571: psOpenNb++;
572: }
573: return ps;
574: }
575:
576: /**
577: * Try to find a PreparedStatement in the pool.
578: * @param sql the given sql query.
579: * @throws SQLException if an error in the database occurs.
580: * @return a given prepared statement.
581: */
582: public PreparedStatement prepareStatement(final String sql)
583: throws SQLException {
584: return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
585: ResultSet.CONCUR_READ_ONLY);
586: }
587:
588: /**
589: * A PreparedStatement has been logically closed.
590: * @param ps a prepared statement.
591: */
592: public void notifyPsClose(final JStatement ps) {
593: logger.debug(ps.getSql());
594: synchronized (psList) {
595: psOpenNb--;
596: if (psList.size() >= pstmtmax) {
597: // Choose a closed element to remove.
598: JStatement lru = null;
599: Iterator i = psList.values().iterator();
600: while (i.hasNext()) {
601: lru = (JStatement) i.next();
602: if (lru.isClosed()) {
603: // actually, remove the first closed element.
604: i.remove();
605: lru.forget();
606: break;
607: }
608: }
609: }
610: }
611: }
612:
613: /**
614: * Notify a Close event on Connection.
615: */
616: @SuppressWarnings("boxing")
617: public void notifyClose() {
618:
619: // Close all PreparedStatement not already closed
620: // When a Connection has been closed, no PreparedStatement should
621: // remain open. This can avoids lack of cursor on some databases.
622: synchronized (psList) {
623: if (psOpenNb > 0) {
624: JStatement jst = null;
625: Iterator i = psList.values().iterator();
626: while (i.hasNext()) {
627: jst = (JStatement) i.next();
628: if (jst.forceClose()) {
629: psOpenNb--;
630: }
631: }
632: if (psOpenNb != 0) {
633: logger.warn("Bad psOpenNb value = {0}", psOpenNb);
634: psOpenNb = 0;
635: }
636: }
637: }
638:
639: // Notify event to listeners
640: for (int i = 0; i < eventListeners.size(); i++) {
641: ConnectionEventListener l = eventListeners.elementAt(i);
642: l.connectionClosed(new ConnectionEvent(this ));
643: }
644: }
645:
646: /**
647: * Notify an Error event on Connection.
648: * @param ex the given exception
649: */
650: public void notifyError(final SQLException ex) {
651: // Notify event to listeners
652: for (int i = 0; i < eventListeners.size(); i++) {
653: ConnectionEventListener l = eventListeners.elementAt(i);
654: l.connectionErrorOccurred(new ConnectionEvent(this, ex));
655: }
656: }
657:
658: }
|