001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.jdbcx;
007:
008: //#ifdef JDK14
009: import java.sql.Connection;
010: import java.sql.ResultSet;
011: import java.sql.SQLException;
012: import java.sql.Statement;
013: import java.util.ArrayList;
014: import java.util.Properties;
015: import javax.sql.ConnectionEvent;
016: import javax.sql.ConnectionEventListener;
017: import javax.sql.XAConnection;
018: import javax.transaction.xa.XAException;
019: import javax.transaction.xa.XAResource;
020: import javax.transaction.xa.Xid;
021: import org.h2.util.ByteUtils;
022: import org.h2.util.JdbcConnectionListener;
023: import org.h2.util.JdbcUtils;
024: import org.h2.jdbc.JdbcConnection; //#endif
025:
026: import org.h2.message.TraceObject;
027:
028: //#ifdef JDK16
029: /*
030: import javax.sql.StatementEventListener;
031: */
032: //#endif
033: /**
034: * This class provides support for distributed transactions.
035: * An application developer usually does not use this interface.
036: * It is used by the transaction manager internally.
037: */
038: public class JdbcXAConnection extends TraceObject
039: //#ifdef JDK14
040: implements XAConnection, XAResource, JdbcConnectionListener
041: //#endif
042: {
043:
044: //#ifdef JDK14
045: private JdbcDataSourceFactory factory;
046: private String url, user, password;
047: private JdbcConnection connSentinel;
048: private JdbcConnection conn;
049: private ArrayList listeners = new ArrayList();
050: private Xid currentTransaction;
051: private int currentTransactionId;
052: private static int nextTransactionId;
053:
054: static {
055: org.h2.Driver.load();
056: }
057:
058: JdbcXAConnection(JdbcDataSourceFactory factory, int id, String url,
059: String user, String password) throws SQLException {
060: this .factory = factory;
061: setTrace(factory.getTrace(), TraceObject.XA_DATA_SOURCE, id);
062: this .url = url;
063: this .user = user;
064: this .password = password;
065: connSentinel = openConnection();
066: getConnection();
067: }
068:
069: //#endif
070:
071: /**
072: * Get the XAResource object.
073: *
074: * @return itself
075: */
076: //#ifdef JDK14
077: public XAResource getXAResource() throws SQLException {
078: debugCodeCall("getXAResource");
079: return this ;
080: }
081:
082: //#endif
083:
084: /**
085: * Close the physical connection.
086: * This method is usually called by the connection pool.
087: */
088: //#ifdef JDK14
089: public void close() throws SQLException {
090: debugCodeCall("close");
091: try {
092: closeConnection(conn);
093: closeConnection(connSentinel);
094: } finally {
095: conn = null;
096: connSentinel = null;
097: }
098: }
099:
100: //#endif
101:
102: /**
103: * Get a new connection. This method is usually called by the connection
104: * pool when there are no more connections in the pool.
105: *
106: * @return the connection
107: */
108: //#ifdef JDK14
109: public Connection getConnection() throws SQLException {
110: debugCodeCall("getConnection");
111: if (conn != null) {
112: closeConnection(conn);
113: conn = null;
114: }
115: conn = openConnection();
116: conn.setJdbcConnectionListener(this );
117: return conn;
118: }
119:
120: //#endif
121:
122: /**
123: * Register a new listener for the connection.
124: *
125: * @param listener the event listener
126: */
127: //#ifdef JDK14
128: public void addConnectionEventListener(
129: ConnectionEventListener listener) {
130: debugCode("addConnectionEventListener(listener)");
131: listeners.add(listener);
132: if (conn != null) {
133: conn.setJdbcConnectionListener(this );
134: }
135: }
136:
137: //#endif
138:
139: /**
140: * Remove the event listener.
141: *
142: * @param listener the event listener
143: */
144: //#ifdef JDK14
145: public void removeConnectionEventListener(
146: ConnectionEventListener listener) {
147: debugCode("removeConnectionEventListener(listener)");
148: listeners.remove(listener);
149: }
150:
151: //#endif
152:
153: /**
154: * INTERNAL
155: */
156: //#ifdef JDK14
157: public void fatalErrorOccurred(JdbcConnection conn, SQLException e)
158: throws SQLException {
159: debugCode("fatalErrorOccurred(conn, e)");
160: for (int i = 0; i < listeners.size(); i++) {
161: ConnectionEventListener listener = (ConnectionEventListener) listeners
162: .get(i);
163: ConnectionEvent event = new ConnectionEvent(this , e);
164: listener.connectionErrorOccurred(event);
165: }
166: close();
167: }
168:
169: //#endif
170:
171: /**
172: * INTERNAL
173: */
174: //#ifdef JDK14
175: public void closed(JdbcConnection conn) {
176: debugCode("closed(conn)");
177: for (int i = 0; i < listeners.size(); i++) {
178: ConnectionEventListener listener = (ConnectionEventListener) listeners
179: .get(i);
180: ConnectionEvent event = new ConnectionEvent(this );
181: listener.connectionClosed(event);
182: }
183: }
184:
185: //#endif
186:
187: /**
188: * Get the transaction timeout.
189: *
190: * @return 0
191: */
192: //#ifdef JDK14
193: public int getTransactionTimeout() throws XAException {
194: debugCodeCall("getTransactionTimeout");
195: return 0;
196: }
197:
198: //#endif
199:
200: /**
201: * Set the transaction timeout.
202: *
203: * @param seconds ignored
204: * @return false
205: */
206: //#ifdef JDK14
207: public boolean setTransactionTimeout(int seconds)
208: throws XAException {
209: debugCodeCall("setTransactionTimeout", seconds);
210: return false;
211: }
212:
213: //#endif
214:
215: /**
216: * Checks if this is the same XAResource.
217: *
218: * @param xares the other object
219: * @return true if this is the same object
220: */
221: //#ifdef JDK14
222: public boolean isSameRM(XAResource xares) throws XAException {
223: debugCode("isSameRM(xares)");
224: return xares == this ;
225: }
226:
227: //#endif
228:
229: /**
230: * Get the list of prepared transaction branches.
231: * This method is called by the transaction manager during recovery.
232: *
233: * @param flag TMSTARTRSCAN, TMENDRSCAN, or TMNOFLAGS. If no other flags are set,
234: * TMNOFLAGS must be used.
235: * @return zero or more Xid objects
236: */
237: //#ifdef JDK14
238: public Xid[] recover(int flag) throws XAException {
239: debugCodeCall("recover", quoteFlags(flag));
240: checkOpen();
241: Statement stat = null;
242: try {
243: stat = conn.createStatement();
244: ResultSet rs = stat
245: .executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT ORDER BY TRANSACTION");
246: ArrayList list = new ArrayList();
247: while (rs.next()) {
248: String tid = rs.getString("TRANSACTION");
249: int id = getNextId(XID);
250: Xid xid = new JdbcXid(factory, id, tid);
251: list.add(xid);
252: }
253: rs.close();
254: Xid[] result = new Xid[list.size()];
255: list.toArray(result);
256: return result;
257: } catch (SQLException e) {
258: getTrace().debug("throw XAException.XAER_RMERR", e);
259: throw new XAException(XAException.XAER_RMERR);
260: } finally {
261: JdbcUtils.closeSilently(stat);
262: }
263: }
264:
265: //#endif
266:
267: /**
268: * Prepare a transaction.
269: *
270: * @param xid the transaction id
271: */
272: //#ifdef JDK14
273: public int prepare(Xid xid) throws XAException {
274: debugCode("prepare(" + quoteXid(xid) + ")");
275: checkOpen();
276: if (!currentTransaction.equals(xid)) {
277: getTrace().debug("throw XAException.XAER_INVAL");
278: throw new XAException(XAException.XAER_INVAL);
279: }
280: Statement stat = null;
281: try {
282: stat = conn.createStatement();
283: currentTransactionId = nextTransactionId++;
284: stat.execute("PREPARE COMMIT TX_" + currentTransactionId);
285: } catch (SQLException e) {
286: throw convertException(e);
287: } finally {
288: JdbcUtils.closeSilently(stat);
289: }
290: getTrace().debug("return XA_OK");
291: return XA_OK;
292: }
293:
294: //#endif
295:
296: /**
297: * Forget a transaction.
298: * This method does not have an effect for this database.
299: *
300: * @param xid the transaction id
301: */
302: //#ifdef JDK14
303: public void forget(Xid xid) throws XAException {
304: debugCode("forget(" + quoteXid(xid) + ")");
305: }
306:
307: //#endif
308:
309: /**
310: * Roll back a transaction.
311: *
312: * @param xid the transaction id
313: */
314: //#ifdef JDK14
315: public void rollback(Xid xid) throws XAException {
316: debugCode("rollback(" + quoteXid(xid) + ")");
317: try {
318: conn.rollback();
319: } catch (SQLException e) {
320: throw convertException(e);
321: }
322: getTrace().debug("rolled back");
323: currentTransaction = null;
324: }
325:
326: //#endif
327:
328: /**
329: * End a transaction.
330: *
331: * @param xid the transaction id
332: * @param flags TMSUCCESS, TMFAIL, or TMSUSPEND
333: */
334: //#ifdef JDK14
335: public void end(Xid xid, int flags) throws XAException {
336: debugCode("end(" + quoteXid(xid) + ", " + quoteFlags(flags)
337: + ")");
338: // TODO transaction end: implement this method
339: if (flags == TMSUSPEND) {
340: return;
341: }
342: if (!currentTransaction.equals(xid)) {
343: getTrace().debug("throw XAException.XAER_OUTSIDE");
344: throw new XAException(XAException.XAER_OUTSIDE);
345: }
346: }
347:
348: //#endif
349:
350: /**
351: * Start or continue to work on a transaction.
352: *
353: * @param xid the transaction id
354: * @param flags TMNOFLAGS, TMJOIN, or TMRESUME
355: */
356: //#ifdef JDK14
357: public void start(Xid xid, int flags) throws XAException {
358: debugCode("start(" + quoteXid(xid) + ", " + quoteFlags(flags)
359: + ")");
360: if (flags == TMRESUME) {
361: return;
362: }
363: if (currentTransaction != null) {
364: getTrace().debug("throw XAException.XAER_NOTA");
365: throw new XAException(XAException.XAER_NOTA);
366: }
367: try {
368: conn.setAutoCommit(false);
369: } catch (SQLException e) {
370: throw convertException(e);
371: }
372: getTrace().debug("currentTransaction=xid");
373: currentTransaction = xid;
374: }
375:
376: //#endif
377:
378: /**
379: * Commit a transaction.
380: *
381: * @param xid the transaction id
382: * @param onePhase use a one-phase protocol if true
383: */
384: //#ifdef JDK14
385: public void commit(Xid xid, boolean onePhase) throws XAException {
386: debugCode("commit(" + quoteXid(xid) + ", " + onePhase + ")");
387: Statement stat = null;
388: try {
389: if (onePhase) {
390: conn.commit();
391: } else {
392: stat = conn.createStatement();
393: stat.execute("COMMIT TRANSACTION TX_"
394: + currentTransactionId);
395: }
396: } catch (SQLException e) {
397: throw convertException(e);
398: } finally {
399: JdbcUtils.closeSilently(stat);
400: }
401: getTrace().debug("committed");
402: currentTransaction = null;
403: }
404:
405: //#endif
406:
407: /**
408: * [Not supported] Add a statement event listener.
409: *
410: * @param listener the new statement event listener
411: */
412: //#ifdef JDK16
413: /*
414: public void addStatementEventListener(StatementEventListener listener) {
415: throw new UnsupportedOperationException();
416: }
417: */
418: //#endif
419: /**
420: * [Not supported] Remove a statement event listener.
421: *
422: * @param listener the statement event listener
423: */
424: //#ifdef JDK16
425: /*
426: public void removeStatementEventListener(StatementEventListener listener) {
427: throw new UnsupportedOperationException();
428: }
429: */
430: //#endif
431: /**
432: * INTERNAL
433: */
434: //#ifdef JDK14
435: public String toString() {
436: return getTraceObjectName() + ": url=" + url + " user=" + user;
437: }
438:
439: private void closeConnection(JdbcConnection conn)
440: throws SQLException {
441: if (conn != null) {
442: conn.closeConnection();
443: }
444: }
445:
446: private JdbcConnection openConnection() throws SQLException {
447: Properties info = new Properties();
448: info.setProperty("user", user);
449: info.setProperty("password", password);
450: JdbcConnection conn = new JdbcConnection(url, info);
451: conn.setJdbcConnectionListener(this );
452: if (currentTransaction != null) {
453: conn.setAutoCommit(false);
454: }
455: return conn;
456: }
457:
458: private XAException convertException(SQLException e) {
459: getTrace().debug("throw XAException(" + e.getMessage() + ")");
460: return new XAException(e.getMessage());
461: }
462:
463: private String quoteXid(Xid xid) {
464: StringBuffer buff = new StringBuffer();
465: buff.append("\"f:");
466: buff.append(xid.getFormatId());
467: buff.append(",bq:");
468: buff.append(ByteUtils.convertBytesToString(xid
469: .getBranchQualifier()));
470: buff.append(",gx:");
471: buff.append(ByteUtils.convertBytesToString(xid
472: .getGlobalTransactionId()));
473: buff.append(",c:");
474: buff.append(xid.getClass().getName());
475: buff.append("\"");
476: return buff.toString();
477: }
478:
479: private String quoteFlags(int flags) {
480: StringBuffer buff = new StringBuffer();
481: if ((flags & XAResource.TMENDRSCAN) != 0) {
482: buff.append("|XAResource.TMENDRSCAN");
483: }
484: if ((flags & XAResource.TMFAIL) != 0) {
485: buff.append("|XAResource.TMFAIL");
486: }
487: if ((flags & XAResource.TMJOIN) != 0) {
488: buff.append("|XAResource.TMJOIN");
489: }
490: if ((flags & XAResource.TMONEPHASE) != 0) {
491: buff.append("|XAResource.TMONEPHASE");
492: }
493: if ((flags & XAResource.TMRESUME) != 0) {
494: buff.append("|XAResource.TMRESUME");
495: }
496: if ((flags & XAResource.TMSTARTRSCAN) != 0) {
497: buff.append("|XAResource.TMSTARTRSCAN");
498: }
499: if ((flags & XAResource.TMSUCCESS) != 0) {
500: buff.append("|XAResource.TMSUCCESS");
501: }
502: if ((flags & XAResource.TMSUSPEND) != 0) {
503: buff.append("|XAResource.TMSUSPEND");
504: }
505: if ((flags & XAResource.XA_RDONLY) != 0) {
506: buff.append("|XAResource.XA_RDONLY");
507: }
508: if (buff.length() == 0) {
509: buff.append("|XAResource.TMNOFLAGS");
510: }
511: return buff.toString().substring(1);
512: }
513:
514: private void checkOpen() throws XAException {
515: if (conn == null) {
516: getTrace().debug("conn==null");
517: throw new XAException(XAException.XAER_RMERR);
518: }
519: }
520: //#endif
521:
522: }
|