001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 1999-2004 Bull S.A.
004: * Contact: jonas-team@objectweb.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 9772 2006-10-20 07:52:56Z durieuxp $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas.dbm;
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.objectweb.jonas.common.Log;
046: import org.objectweb.transaction.jta.ResourceManagerEvent;
047: import org.objectweb.util.monolog.api.BasicLevel;
048: import org.objectweb.util.monolog.api.Logger;
049:
050: /**
051: * This class represents the physical connection, managed by the pool.
052: * It implements the PooledConnection interface, in fact via the derived
053: * XAConnection interface.
054: * @author durieuxp
055: */
056: public class JManagedConnection implements Comparable, XAConnection,
057: XAResource, ResourceManagerEvent, Synchronization {
058:
059: static private Logger logger = Log.getLogger(Log.JONAS_DBM_PREFIX);
060: static private Logger loggerxa = Log.getLogger(Log.JONAS_DBM_PREFIX
061: + ".xa");
062: static private Logger loggerps = Log.getLogger(Log.JONAS_DBM_PREFIX
063: + ".ps");
064:
065: Connection implConn = null;
066: Connection actConn = null;
067: ConnectionManager ds;
068: private int pstmtmax;
069:
070: // Most of the time, only 1 element, but must keep a Vector here.
071: private Vector eventListeners = new Vector();
072:
073: private int timeout;
074: private String rmid = null;
075:
076: private int open; // >0 if connection is open
077: private Transaction tx; // transaction the connection is involved with
078: private Transaction enlistedInTx; /// current transaction in case of late enlistment
079: private boolean rme = false; // true if registered as ResourceManagerEvent
080: private long deathTime; // When it is expected to die (for closed connections)
081: private long closeTime; // When it is expected to be closed (for opened connections)
082:
083: private static int objcount = 10; // to have a minimum of prepare statements.
084: private int ident;
085: private int hitCount = 0;
086:
087: /**
088: * List of PreparedStatement in the pool.
089: */
090: private Map psList = null;
091:
092: // -----------------------------------------------------------------
093: // Constructors
094: // -----------------------------------------------------------------
095:
096: public JManagedConnection(Connection conn, ConnectionManager ds) {
097:
098: logger.log(BasicLevel.DEBUG, "constructor");
099:
100: this .actConn = conn;
101: this .ds = ds;
102:
103: // An XAConnection holds 2 objects: 1 Connection + 1 XAResource
104: this .implConn = new JConnection(this , conn);
105: this .rmid = ds.getDatasourceName();
106:
107: open = 0;
108: deathTime = System.currentTimeMillis() + ds.getMaxAgeMilli();
109:
110: // getPstmtMax() = 0 -> no pool for PreparedStatement's
111: // Not all connections have the same number of prepare statement.
112: ident = objcount++;
113: if (ds.getPstmtMax() > 0) {
114: int pstmtmin = 1 + ds.getPstmtMax() / 4;
115: pstmtmax = ident % ds.getPstmtMax();
116: if (pstmtmax < pstmtmin) {
117: pstmtmax = pstmtmin;
118: }
119: psList = Collections.synchronizedMap(new HashMap(pstmtmax));
120: }
121: }
122:
123: /**
124: * @return The ident of this JManagedConnection
125: */
126: public int getIdent() {
127: return ident;
128: }
129:
130: /**
131: * Dynamically change the prepared statement pool size
132: */
133: public void setPstmtMax(int max) {
134: pstmtmax = max;
135: if (psList == null) {
136: psList = Collections.synchronizedMap(new HashMap(pstmtmax));
137: }
138: }
139:
140: // -----------------------------------------------------------------
141: // XAResource implementation
142: // -----------------------------------------------------------------
143:
144: /**
145: * Commit the global transaction specified by xid.
146: * @param xid transaction xid
147: * @param onePhase true if one phase commit
148: * @throws XAException XA protocol error
149: */
150: public void commit(Xid xid, boolean onePhase) throws XAException {
151: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
152: loggerxa.log(BasicLevel.DEBUG, "XA-COMMIT for " + xid);
153: }
154:
155: // Avoids a NPE.
156: // This should not occur because a close could not be called
157: // on a connection implied inside a transaction.
158: if (actConn == null) {
159: loggerxa.log(BasicLevel.ERROR,
160: "commit on a closed connection");
161: return;
162: }
163:
164: // Commit the transaction
165: try {
166: actConn.commit();
167: } catch (SQLException e) {
168: loggerxa.log(BasicLevel.ERROR, "Cannot commit transaction:"
169: + e);
170: notifyError(e);
171: throw (new XAException("Error on commit"));
172: }
173: }
174:
175: /**
176: * Ends the work performed on behalf of a transaction branch.
177: * @param xid transaction xid
178: * @param flags currently unused
179: * @throws XAException XA protocol error
180: */
181: public void end(Xid xid, int flags) throws XAException {
182: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
183: loggerxa.log(BasicLevel.DEBUG, "XA-END for " + xid);
184: }
185: }
186:
187: /**
188: * Tell the resource manager to forget about a heuristically
189: * completed transaction branch.
190: * @param xid transaction xid
191: * @throws XAException XA protocol error
192: */
193: public void forget(Xid xid) throws XAException {
194: // not implemented.
195: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
196: loggerxa.log(BasicLevel.DEBUG, "XA-FORGET for " + xid);
197: }
198: }
199:
200: /**
201: * Obtain the current transaction timeout value set for this
202: * XAResource instance.
203: * @return the current transaction timeout in seconds
204: * @throws XAException XA protocol error
205: */
206: public int getTransactionTimeout() throws XAException {
207: if (logger.isLoggable(BasicLevel.DEBUG)) {
208: logger.log(BasicLevel.DEBUG, "getTransactionTimeout for "
209: + this );
210: }
211: return timeout;
212: }
213:
214: /** Determine if the resource manager instance represented by the
215: * target object is the same as the resource manager instance
216: * represented by the parameter xares
217: * @param xares An XAResource object
218: * @return True if same RM instance, otherwise false.
219: * @throws XAException XA protocol error
220: */
221: public boolean isSameRM(XAResource xares) throws XAException {
222:
223: // In this pseudo-driver, we must return true only if
224: // both objects refer to the same XAResource, and not
225: // the same Resource Manager, because actually, we must
226: // send commit/rollback on each XAResource involved in
227: // the transaction.
228: if (xares.equals(this )) {
229: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
230: loggerxa.log(BasicLevel.DEBUG, "isSameRM = true "
231: + this );
232: }
233: return true;
234: }
235: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
236: loggerxa.log(BasicLevel.DEBUG, "isSameRM = false " + this );
237: }
238: return false;
239: }
240:
241: /**
242: * Ask the resource manager to prepare for a transaction commit
243: * of the transaction specified in xid.
244: * @param xid transaction xid
245: * @throws XAException XA protocol error
246: */
247: public int prepare(Xid xid) throws XAException {
248:
249: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
250: loggerxa.log(BasicLevel.DEBUG, "XA-PREPARE for " + xid);
251: }
252: // No 2PC on standard JDBC drivers
253: return XA_OK;
254: }
255:
256: /**
257: * Obtain a list of prepared transaction branches from a resource
258: * manager.
259: * @return an array of transaction Xids
260: * @throws XAException XA protocol error
261: */
262: public Xid[] recover(int flag) throws XAException {
263:
264: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
265: loggerxa.log(BasicLevel.DEBUG, "XA-RECOVER for " + this );
266: }
267: // Not implemented
268: return null;
269: }
270:
271: /**
272: * Inform the resource manager to roll back work done on behalf
273: * of a transaction branch
274: * @param xid transaction xid
275: * @throws XAException XA protocol error
276: */
277: public void rollback(Xid xid) throws XAException {
278:
279: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
280: loggerxa.log(BasicLevel.DEBUG, "XA-ROLLBACK for " + xid);
281: }
282:
283: // Avoids a NPE.
284: // This should not occur because a close could not be called
285: // on a connection implied inside a transaction.
286: if (actConn == null) {
287: loggerxa.log(BasicLevel.ERROR,
288: "rollback on a closed connection");
289: return;
290: }
291:
292: // Make sure that we are not in AutoCommit mode
293: try {
294: if (actConn.getAutoCommit() == true) {
295: loggerxa
296: .log(BasicLevel.ERROR,
297: "Rollback called on XAResource with AutoCommit set");
298: throw (new XAException(XAException.XA_HEURCOM));
299: }
300: } catch (SQLException e) {
301: loggerxa.log(BasicLevel.ERROR, "Cannot getAutoCommit:" + e);
302: notifyError(e);
303: throw (new XAException("Error on getAutoCommit"));
304: }
305:
306: // Rollback the transaction
307: try {
308: actConn.rollback();
309: } catch (SQLException e) {
310: loggerxa.log(BasicLevel.ERROR,
311: "Cannot rollback transaction:" + e);
312: notifyError(e);
313: throw (new XAException("Error on rollback"));
314: }
315: }
316:
317: /**
318: * Set the current transaction timeout value for this XAResource
319: * instance.
320: * @param seconds timeout value, in seconds.
321: * @return always true
322: * @throws XAException XA protocol error
323: */
324: public boolean setTransactionTimeout(int seconds)
325: throws XAException {
326:
327: if (logger.isLoggable(BasicLevel.DEBUG)) {
328: logger.log(BasicLevel.DEBUG, "setTransactionTimeout "
329: + this );
330: }
331: timeout = seconds;
332: return true;
333: }
334:
335: /**
336: * Start work on behalf of a transaction branch specified in xid
337: * @param xid transaction xid
338: * @throws XAException XA protocol error
339: */
340: public void start(Xid xid, int flags) throws XAException {
341:
342: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
343: loggerxa.log(BasicLevel.DEBUG, "XA-START for " + xid);
344: }
345: }
346:
347: // -----------------------------------------------------------------
348: // XAConnection implementation
349: // -----------------------------------------------------------------
350:
351: /**
352: * Return an XA resource to the caller.
353: * @return The XAResource
354: * @exception SQLException - if a database-access error occurs
355: */
356: public XAResource getXAResource() throws SQLException {
357: return this ;
358: }
359:
360: // -----------------------------------------------------------------
361: // Comparable implementation
362: // -----------------------------------------------------------------
363:
364: public int compareTo(Object o) {
365: JManagedConnection other = (JManagedConnection) o;
366: int diff = psNumber() - other.psNumber();
367: if (diff == 0) {
368: return getIdent() - other.getIdent();
369: } else {
370: return diff;
371: }
372: }
373:
374: public int psNumber() {
375: return hitCount;
376: }
377:
378: // -----------------------------------------------------------------
379: // PooledConnection implementation
380: // -----------------------------------------------------------------
381:
382: /**
383: * Create an object handle for a database connection.
384: * @exception SQLException - if a database-access error occurs
385: */
386: public Connection getConnection() throws SQLException {
387: // Just return the already created object.
388: return implConn;
389: }
390:
391: /**
392: * Close the database connection.
393: *
394: * @exception SQLException - if a database-access error occurs
395: */
396: public void close() throws SQLException {
397: logger.log(BasicLevel.DEBUG, "");
398:
399: // Close the actual Connection here.
400: if (actConn != null) {
401: actConn.close();
402: } else {
403: logger.log(BasicLevel.ERROR, "Connection already closed");
404: }
405: actConn = null;
406: implConn = null;
407: }
408:
409: /**
410: * Add an event listener.
411: *
412: * @param listener event listener
413: */
414: public void addConnectionEventListener(
415: ConnectionEventListener listener) {
416: logger.log(BasicLevel.DEBUG, "");
417: eventListeners.addElement(listener);
418: }
419:
420: /**
421: * Remove an event listener.
422: *
423: * @param listener event listener
424: */
425: public void removeConnectionEventListener(
426: ConnectionEventListener listener) {
427: logger.log(BasicLevel.DEBUG, "");
428: eventListeners.removeElement(listener);
429: }
430:
431: /**
432: * implementation of resource manager event
433: */
434: public void enlistConnection(Transaction transaction)
435: throws javax.transaction.SystemException {
436: try {
437: if (rme) {
438: if (implConn == null) {
439: loggerxa.log(BasicLevel.ERROR,
440: "Cannot enlist a closed connection");
441: return;
442: }
443: implConn.setAutoCommit(false);
444: // the tx value of the pool item does not match with the enlisted value !?
445: enlistedInTx = transaction;
446: // enlist the resource
447: if (loggerxa.isLoggable(BasicLevel.DEBUG)) {
448: loggerxa.log(BasicLevel.DEBUG,
449: "enlist XAResource on " + transaction);
450: }
451: transaction.enlistResource(getXAResource());
452: }
453: } catch (javax.transaction.RollbackException e) {
454: javax.transaction.SystemException se = new javax.transaction.SystemException(
455: "Unexpected RollbackException exception");
456: se.initCause(e);
457: throw se;
458: } catch (java.sql.SQLException e) {
459: javax.transaction.SystemException se = new javax.transaction.SystemException(
460: "Unexpected SQL exception");
461: se.initCause(e);
462: throw se;
463: }
464: }
465:
466: /**
467: * synchronization implementation
468: */
469: public void beforeCompletion() {
470: // nothing to do
471: }
472:
473: /**
474: * synchronization implementation
475: */
476: public void afterCompletion(int status) {
477: if (tx == null) {
478: loggerxa.log(BasicLevel.ERROR, "NO TX!");
479: }
480: ds.freeConnections(tx != null ? tx : enlistedInTx);
481: }
482:
483: /**
484: * @return true if connection max age has expired
485: */
486: public boolean isAged() {
487: return (deathTime < System.currentTimeMillis());
488: }
489:
490: /**
491: * @return true if connection is still open
492: */
493: public boolean isOpen() {
494: return (open > 0);
495: }
496:
497: /**
498: * @return open count
499: */
500: public int getOpenCount() {
501: return open;
502: }
503:
504: /**
505: * Check if the connection has been unused for too long time.
506: * This occurs usually when the caller forgot to call close().
507: * @return true if open time has been reached, and not involved in a tx.
508: */
509: public boolean inactive() {
510: return (open > 0 && tx == null && enlistedInTx == null && closeTime < System
511: .currentTimeMillis());
512: }
513:
514: /**
515: * @return true if connection is closed
516: */
517: public boolean isClosed() {
518: return (open <= 0);
519: }
520:
521: /**
522: * Notify as opened
523: */
524: public void hold() {
525: open++;
526: closeTime = System.currentTimeMillis()
527: + ds.getMaxOpenTimeMilli();
528: }
529:
530: /**
531: * notify as closed
532: * @return true if normal close.
533: */
534: public boolean release() {
535: open--;
536: if (open < 0) {
537: logger
538: .log(BasicLevel.WARN,
539: "connection was already closed");
540: open = 0;
541: return false;
542: }
543: if (tx == null && open > 0) {
544: logger.log(BasicLevel.ERROR,
545: "connection-open counter overflow");
546: open = 0;
547: }
548:
549: // Close all PreparedStatement not already closed
550: // When a Connection has been closed, no PreparedStatement should
551: // remain open. This can avoids lack of cursor on some databases.
552: if (open == 0 && pstmtmax > 0) {
553: synchronized (psList) {
554: JStatement jst = null;
555: Iterator i = psList.values().iterator();
556: while (i.hasNext()) {
557: jst = (JStatement) i.next();
558: jst.forceClose();
559: }
560: }
561: }
562:
563: return true;
564: }
565:
566: /**
567: * Set the associated transaction
568: * @param tx Transaction
569: */
570: public void setTx(Transaction tx) {
571: this .tx = tx;
572: }
573:
574: /**
575: * @return the Transaction
576: */
577: public Transaction getTx() {
578: return tx;
579: }
580:
581: /**
582: * @return true if registered as RME
583: */
584: public boolean isRME() {
585: return rme;
586: }
587:
588: /**
589: * set/unset as RME
590: */
591: public void setRME(boolean rme) {
592: this .rme = rme;
593: }
594:
595: /**
596: * remove this item, ignoring exception on close.
597: */
598: public void remove() {
599: // Close the physical connection
600: try {
601: close();
602: } catch (java.sql.SQLException ign) {
603: logger.log(BasicLevel.ERROR,
604: "Could not close Connection: ", ign);
605: }
606:
607: // remove all references (for GC)
608: tx = null;
609: enlistedInTx = null;
610: }
611:
612: // -----------------------------------------------------------------
613: // Other methods
614: // -----------------------------------------------------------------
615:
616: /**
617: * Try to find a PreparedStatement in the pool
618: */
619: public PreparedStatement prepareStatement(String sql,
620: int resultSetType, int resultSetConcurrency)
621: throws SQLException {
622:
623: loggerps.log(BasicLevel.DEBUG, sql);
624: if (pstmtmax == 0) {
625: return actConn.prepareStatement(sql, resultSetType,
626: resultSetConcurrency);
627: }
628: JStatement ps = null;
629: synchronized (psList) {
630: ps = (JStatement) psList.get(sql);
631: if (ps != null) {
632: if (!ps.isClosed()) {
633: loggerps
634: .log(BasicLevel.WARN, "reuse an open pstmt");
635: }
636: ps.reuse();
637: hitCount++;
638: } else {
639: // Not found in cache. Create a new one.
640: PreparedStatement aps = actConn.prepareStatement(sql,
641: resultSetType, resultSetConcurrency);
642: ps = new JStatement(aps, this , sql);
643: psList.put(sql, ps);
644: }
645: }
646: return ps;
647: }
648:
649: /**
650: * Try to find a PreparedStatement in the pool
651: */
652: public PreparedStatement prepareStatement(String sql)
653: throws SQLException {
654: return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY,
655: ResultSet.CONCUR_READ_ONLY);
656: }
657:
658: /**
659: * A PreparedStatement has been logically closed.
660: * @return
661: */
662: public void notifyPsClose(JStatement ps) {
663: loggerps.log(BasicLevel.DEBUG, ps.getSql());
664: if (pstmtmax == 0) {
665: return;
666: }
667: synchronized (psList) {
668: if (psList.size() >= pstmtmax) {
669: // Choose a closed element to remove.
670: // TODO: Should be the LRU.
671: JStatement lru = null;
672: Iterator i = psList.values().iterator();
673: while (i.hasNext()) {
674: lru = (JStatement) i.next();
675: if (lru.isClosed()) {
676: // actually, remove the first closed element.
677: i.remove();
678: lru.forget();
679: break;
680: }
681: }
682: }
683: }
684: }
685:
686: /*
687: * get the RMID
688: * @return the rmid.
689: */
690: public String getRMID() {
691: return rmid;
692: }
693:
694: /*
695: * Notify a Close event on Connection
696: */
697: public void notifyClose() {
698: logger.log(BasicLevel.DEBUG, "");
699:
700: // Notify event to listeners
701: logger.log(BasicLevel.DEBUG, "");
702: for (int i = 0; i < eventListeners.size(); i++) {
703: ConnectionEventListener l = (ConnectionEventListener) eventListeners
704: .elementAt(i);
705: l.connectionClosed(new ConnectionEvent(this ));
706: }
707: }
708:
709: /*
710: * Notify an Error event on Connection
711: */
712: public void notifyError(SQLException ex) {
713: logger.log(BasicLevel.DEBUG, "");
714:
715: // Notify event to listeners
716: for (int i = 0; i < eventListeners.size(); i++) {
717: ConnectionEventListener l = (ConnectionEventListener) eventListeners
718: .elementAt(i);
719: l.connectionErrorOccurred(new ConnectionEvent(this, ex));
720: }
721: }
722:
723: }
|