001: //jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: //Copyright (C) 2004 The jTDS Project
003: //
004: //This library is free software; you can redistribute it and/or
005: //modify it under the terms of the GNU Lesser General Public
006: //License as published by the Free Software Foundation; either
007: //version 2.1 of the License, or (at your option) any later version.
008: //
009: //This library is distributed in the hope that it will be useful,
010: //but WITHOUT ANY WARRANTY; without even the implied warranty of
011: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: //Lesser General Public License for more details.
013: //
014: //You should have received a copy of the GNU Lesser General Public
015: //License along with this library; if not, write to the Free Software
016: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc;
019:
020: import java.sql.Connection;
021: import java.sql.SQLException;
022: import javax.transaction.xa.XAException;
023: import javax.transaction.xa.XAResource;
024: import javax.transaction.xa.Xid;
025:
026: import net.sourceforge.jtds.jdbcx.JtdsXid;
027: import net.sourceforge.jtds.util.Logger;
028:
029: /**
030: * This class contains static utility methods used to implement distributed transactions.
031: * For SQL Server 2000 the driver can provide true distributed transactions provided that
032: * the external stored procedure in JtdsXA.dll is installed. For other types of server
033: * only an emulation is available at this stage.
034: */
035: public class XASupport {
036: /**
037: * The Resource Manager ID allocated by jTDS
038: */
039: private static final int XA_RMID = 1;
040: /**
041: * xa_open login string unique to jTDS.
042: */
043: private static final String TM_ID = "TM=JTDS,RmRecoveryGuid=434CDE1A-F747-4942-9584-04937455CAB4";
044: //
045: // XA Switch constants
046: //
047: private static final int XA_OPEN = 1;
048: private static final int XA_CLOSE = 2;
049: private static final int XA_START = 3;
050: private static final int XA_END = 4;
051: private static final int XA_ROLLBACK = 5;
052: private static final int XA_PREPARE = 6;
053: private static final int XA_COMMIT = 7;
054: private static final int XA_RECOVER = 8;
055: private static final int XA_FORGET = 9;
056: private static final int XA_COMPLETE = 10;
057: /**
058: * Set this field to 1 to enable XA tracing.
059: */
060: private static final int XA_TRACE = 0;
061:
062: //
063: // ----- XA support routines -----
064: //
065: /**
066: * Invoke the xa_open routine on the SQL Server.
067: *
068: * @param connection the parent XAConnection object
069: * @return the XA connection ID allocated by xp_jtdsxa
070: */
071: public static int xa_open(Connection connection)
072: throws SQLException {
073:
074: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
075: if (con.isXaEmulation()) {
076: //
077: // Emulate xa_open method
078: //
079: Logger
080: .println("xa_open: emulating distributed transaction support");
081: if (con.getXid() != null) {
082: throw new SQLException(Messages.get(
083: "error.xasupport.activetran", "xa_open"),
084: "HY000");
085: }
086: con.setXaState(XA_OPEN);
087: return 0;
088: }
089: //
090: // Execute xa_open via MSDTC
091: //
092: // Check that we are using SQL Server 2000+
093: //
094: if (((ConnectionJDBC2) connection).getServerType() != Driver.SQLSERVER
095: || ((ConnectionJDBC2) connection).getTdsVersion() < Driver.TDS80) {
096: throw new SQLException(Messages
097: .get("error.xasupport.nodist"), "HY000");
098: }
099: Logger
100: .println("xa_open: Using SQL2000 MSDTC to support distributed transactions");
101: //
102: // OK Now invoke extended stored procedure to register this connection.
103: //
104: int args[] = new int[5];
105: args[1] = XA_OPEN;
106: args[2] = XA_TRACE;
107: args[3] = XA_RMID;
108: args[4] = XAResource.TMNOFLAGS;
109: byte[][] id;
110: id = ((ConnectionJDBC2) connection).sendXaPacket(args, TM_ID
111: .getBytes());
112: if (args[0] != XAResource.XA_OK || id == null || id[0] == null
113: || id[0].length != 4) {
114: throw new SQLException(Messages
115: .get("error.xasupport.badopen"), "HY000");
116: }
117: return (id[0][0] & 0xFF) | ((id[0][1] & 0xFF) << 8)
118: | ((id[0][2] & 0xFF) << 16) | ((id[0][3] & 0xFF) << 24);
119: }
120:
121: /**
122: * Invoke the xa_close routine on the SQL Server.
123: *
124: * @param connection JDBC Connection to be enlisted in the transaction
125: * @param xaConId the connection ID allocated by the server
126: */
127: public static void xa_close(Connection connection, int xaConId)
128: throws SQLException {
129:
130: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
131: if (con.isXaEmulation()) {
132: //
133: // Emulate xa_close method
134: //
135: con.setXaState(0);
136: if (con.getXid() != null) {
137: con.setXid(null);
138: try {
139: con.rollback();
140: } catch (SQLException e) {
141: Logger
142: .println("xa_close: rollback() returned "
143: + e);
144: }
145: try {
146: con.setAutoCommit(true);
147: } catch (SQLException e) {
148: Logger
149: .println("xa_close: setAutoCommit() returned "
150: + e);
151: }
152: throw new SQLException(Messages.get(
153: "error.xasupport.activetran", "xa_close"),
154: "HY000");
155: }
156: return;
157: }
158: //
159: // Execute xa_close via MSDTC
160: //
161: int args[] = new int[5];
162: args[1] = XA_CLOSE;
163: args[2] = xaConId;
164: args[3] = XA_RMID;
165: args[4] = XAResource.TMNOFLAGS;
166: ((ConnectionJDBC2) connection).sendXaPacket(args, TM_ID
167: .getBytes());
168: }
169:
170: /**
171: * Invoke the xa_start routine on the SQL Server.
172: *
173: * @param connection JDBC Connection to be enlisted in the transaction
174: * @param xaConId the connection ID allocated by the server
175: * @param xid the XA Transaction ID object
176: * @param flags XA Flags for start command
177: * @exception javax.transaction.xa.XAException
178: * if an error condition occurs
179: */
180: public static void xa_start(Connection connection, int xaConId,
181: Xid xid, int flags) throws XAException {
182:
183: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
184: if (con.isXaEmulation()) {
185: //
186: // Emulate xa_start method
187: //
188: JtdsXid lxid = new JtdsXid(xid);
189: if (con.getXaState() == 0) {
190: // Connection not opened
191: raiseXAException(XAException.XAER_PROTO);
192: }
193: JtdsXid tran = (JtdsXid) con.getXid();
194: if (tran != null) {
195: if (tran.equals(lxid)) {
196: raiseXAException(XAException.XAER_DUPID);
197: } else {
198: raiseXAException(XAException.XAER_PROTO);
199: }
200: }
201: if (flags != XAResource.TMNOFLAGS) {
202: // TMJOIN and TMRESUME cannot be supported
203: raiseXAException(XAException.XAER_INVAL);
204: }
205: try {
206: connection.setAutoCommit(false);
207: } catch (SQLException e) {
208: raiseXAException(XAException.XAER_RMERR);
209: }
210: con.setXid(lxid);
211: con.setXaState(XA_START);
212: return;
213: }
214: //
215: // Execute xa_start via MSDTC
216: //
217: int args[] = new int[5];
218: args[1] = XA_START;
219: args[2] = xaConId;
220: args[3] = XA_RMID;
221: args[4] = flags;
222: byte[][] cookie;
223: try {
224: cookie = ((ConnectionJDBC2) connection).sendXaPacket(args,
225: toBytesXid(xid));
226: if (args[0] == XAResource.XA_OK && cookie != null) {
227: ((ConnectionJDBC2) connection)
228: .enlistConnection(cookie[0]);
229: }
230: } catch (SQLException e) {
231: raiseXAException(e);
232: }
233: if (args[0] != XAResource.XA_OK) {
234: raiseXAException(args[0]);
235: }
236: }
237:
238: /**
239: * Invoke the xa_end routine on the SQL Server.
240: *
241: * @param connection JDBC Connection enlisted in the transaction
242: * @param xaConId the connection ID allocated by the server
243: * @param xid the XA Transaction ID object
244: * @param flags XA Flags for start command
245: * @exception javax.transaction.xa.XAException
246: * if an error condition occurs
247: */
248: public static void xa_end(Connection connection, int xaConId,
249: Xid xid, int flags) throws XAException {
250:
251: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
252: if (con.isXaEmulation()) {
253: //
254: // Emulate xa_end method
255: //
256: JtdsXid lxid = new JtdsXid(xid);
257: if (con.getXaState() != XA_START) {
258: // Connection not started
259: raiseXAException(XAException.XAER_PROTO);
260: }
261: JtdsXid tran = (JtdsXid) con.getXid();
262: if (tran == null || !tran.equals(lxid)) {
263: raiseXAException(XAException.XAER_NOTA);
264: }
265: if (flags != XAResource.TMSUCCESS
266: && flags != XAResource.TMFAIL) {
267: // TMSUSPEND and TMMIGRATE cannot be supported
268: raiseXAException(XAException.XAER_INVAL);
269: }
270: con.setXaState(XA_END);
271: return;
272: }
273: //
274: // Execute xa_end via MSDTC
275: //
276: int args[] = new int[5];
277: args[1] = XA_END;
278: args[2] = xaConId;
279: args[3] = XA_RMID;
280: args[4] = flags;
281: try {
282: ((ConnectionJDBC2) connection).sendXaPacket(args,
283: toBytesXid(xid));
284: ((ConnectionJDBC2) connection).enlistConnection(null);
285: } catch (SQLException e) {
286: raiseXAException(e);
287: }
288: if (args[0] != XAResource.XA_OK) {
289: raiseXAException(args[0]);
290: }
291: }
292:
293: /**
294: * Invoke the xa_prepare routine on the SQL Server.
295: *
296: * @param connection JDBC Connection enlisted in the transaction.
297: * @param xaConId The connection ID allocated by the server.
298: * @param xid The XA Transaction ID object.
299: * @return prepare status (XA_OK or XA_RDONLY) as an <code>int</code>.
300: * @exception javax.transaction.xa.XAException
301: * if an error condition occurs
302: */
303: public static int xa_prepare(Connection connection, int xaConId,
304: Xid xid) throws XAException {
305:
306: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
307: if (con.isXaEmulation()) {
308: //
309: // Emulate xa_prepare method
310: // In emulation mode this is essentially a noop as we
311: // are not able to offer true two phase commit.
312: //
313: JtdsXid lxid = new JtdsXid(xid);
314: if (con.getXaState() != XA_END) {
315: // Connection not ended
316: raiseXAException(XAException.XAER_PROTO);
317: }
318: JtdsXid tran = (JtdsXid) con.getXid();
319: if (tran == null || !tran.equals(lxid)) {
320: raiseXAException(XAException.XAER_NOTA);
321: }
322: con.setXaState(XA_PREPARE);
323: Logger
324: .println("xa_prepare: Warning: Two phase commit not available in XA emulation mode.");
325: return XAResource.XA_OK;
326: }
327: //
328: // Execute xa_prepare via MSDTC
329: //
330: int args[] = new int[5];
331: args[1] = XA_PREPARE;
332: args[2] = xaConId;
333: args[3] = XA_RMID;
334: args[4] = XAResource.TMNOFLAGS;
335: try {
336: ((ConnectionJDBC2) connection).sendXaPacket(args,
337: toBytesXid(xid));
338: } catch (SQLException e) {
339: raiseXAException(e);
340: }
341: if (args[0] != XAResource.XA_OK
342: && args[0] != XAResource.XA_RDONLY) {
343: raiseXAException(args[0]);
344: }
345: return args[0];
346: }
347:
348: /**
349: * Invoke the xa_commit routine on the SQL Server.
350: *
351: * @param connection JDBC Connection enlisted in the transaction
352: * @param xaConId the connection ID allocated by the server
353: * @param xid the XA Transaction ID object
354: * @param onePhase <code>true</code> if single phase commit required
355: * @exception javax.transaction.xa.XAException
356: * if an error condition occurs
357: */
358: public static void xa_commit(Connection connection, int xaConId,
359: Xid xid, boolean onePhase) throws XAException {
360:
361: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
362: if (con.isXaEmulation()) {
363: //
364: // Emulate xa_commit method
365: //
366: JtdsXid lxid = new JtdsXid(xid);
367: if (con.getXaState() != XA_END
368: && con.getXaState() != XA_PREPARE) {
369: // Connection not ended or prepared
370: raiseXAException(XAException.XAER_PROTO);
371: }
372: JtdsXid tran = (JtdsXid) con.getXid();
373: if (tran == null || !tran.equals(lxid)) {
374: raiseXAException(XAException.XAER_NOTA);
375: }
376: con.setXid(null);
377: try {
378: con.commit();
379: } catch (SQLException e) {
380: raiseXAException(e);
381: } finally {
382: try {
383: con.setAutoCommit(true);
384: } catch (SQLException e) {
385: Logger
386: .println("xa_close: setAutoCommit() returned "
387: + e);
388: }
389: con.setXaState(XA_OPEN);
390: }
391: return;
392: }
393: //
394: // Execute xa_commit via MSDTC
395: //
396: int args[] = new int[5];
397: args[1] = XA_COMMIT;
398: args[2] = xaConId;
399: args[3] = XA_RMID;
400: args[4] = (onePhase) ? XAResource.TMONEPHASE
401: : XAResource.TMNOFLAGS;
402: try {
403: ((ConnectionJDBC2) connection).sendXaPacket(args,
404: toBytesXid(xid));
405: } catch (SQLException e) {
406: raiseXAException(e);
407: }
408: if (args[0] != XAResource.XA_OK) {
409: raiseXAException(args[0]);
410: }
411: }
412:
413: /**
414: * Invoke the xa_rollback routine on the SQL Server.
415: *
416: * @param connection JDBC Connection enlisted in the transaction
417: * @param xaConId the connection ID allocated by the server
418: * @param xid the XA Transaction ID object
419: * @exception javax.transaction.xa.XAException
420: * if an error condition occurs
421: */
422: public static void xa_rollback(Connection connection, int xaConId,
423: Xid xid) throws XAException {
424:
425: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
426: if (con.isXaEmulation()) {
427: //
428: // Emulate xa_rollback method
429: //
430: JtdsXid lxid = new JtdsXid(xid);
431: if (con.getXaState() != XA_END
432: && con.getXaState() != XA_PREPARE) {
433: // Connection not ended
434: raiseXAException(XAException.XAER_PROTO);
435: }
436: JtdsXid tran = (JtdsXid) con.getXid();
437: if (tran == null || !tran.equals(lxid)) {
438: raiseXAException(XAException.XAER_NOTA);
439: }
440: con.setXid(null);
441: try {
442: con.rollback();
443: } catch (SQLException e) {
444: raiseXAException(e);
445: } finally {
446: try {
447: con.setAutoCommit(true);
448: } catch (SQLException e) {
449: Logger
450: .println("xa_close: setAutoCommit() returned "
451: + e);
452: }
453: con.setXaState(XA_OPEN);
454: }
455: return;
456: }
457: //
458: // Execute xa_rollback via MSDTC
459: //
460: int args[] = new int[5];
461: args[1] = XA_ROLLBACK;
462: args[2] = xaConId;
463: args[3] = XA_RMID;
464: args[4] = XAResource.TMNOFLAGS;
465: try {
466: ((ConnectionJDBC2) connection).sendXaPacket(args,
467: toBytesXid(xid));
468: } catch (SQLException e) {
469: raiseXAException(e);
470: }
471: if (args[0] != XAResource.XA_OK) {
472: raiseXAException(args[0]);
473: }
474: }
475:
476: /**
477: * Invoke the xa_recover routine on the SQL Server.
478: * <p/>
479: * This version of xa_recover will return all XIDs on the first call.
480: *
481: * @param connection JDBC Connection enlisted in the transaction
482: * @param xaConId the connection ID allocated by the server
483: * @param flags XA Flags for start command
484: * @return transactions to recover as a <code>Xid[]</code>
485: * @exception javax.transaction.xa.XAException
486: * if an error condition occurs
487: */
488: public static Xid[] xa_recover(Connection connection, int xaConId,
489: int flags) throws XAException {
490:
491: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
492: if (con.isXaEmulation()) {
493: //
494: // Emulate xa_recover method
495: //
496: // There is no state available all uncommited transactions
497: // will have been rolled back by the server.
498: if (flags != XAResource.TMSTARTRSCAN
499: && flags != XAResource.TMENDRSCAN
500: && flags != XAResource.TMNOFLAGS) {
501: raiseXAException(XAException.XAER_INVAL);
502: }
503: return new JtdsXid[0];
504: }
505: //
506: // Execute xa_recover via MSDTC
507: //
508: int args[] = new int[5];
509: args[1] = XA_RECOVER;
510: args[2] = xaConId;
511: args[3] = XA_RMID;
512: args[4] = XAResource.TMNOFLAGS;
513: Xid[] list = null;
514:
515: if (flags != XAResource.TMSTARTRSCAN) {
516: return new JtdsXid[0];
517: }
518:
519: try {
520: byte[][] buffer = ((ConnectionJDBC2) connection)
521: .sendXaPacket(args, null);
522: if (args[0] >= 0) {
523: int n = buffer.length;
524: list = new JtdsXid[n];
525: for (int i = 0; i < n; i++) {
526: list[i] = new JtdsXid(buffer[i], 0);
527: }
528: }
529: } catch (SQLException e) {
530: raiseXAException(e);
531: }
532: if (args[0] < 0) {
533: raiseXAException(args[0]);
534: }
535: if (list == null) {
536: list = new JtdsXid[0];
537: }
538: return list;
539: }
540:
541: /**
542: * Invoke the xa_forget routine on the SQL Server.
543: *
544: * @param connection JDBC Connection enlisted in the transaction
545: * @param xaConId the connection ID allocated by the server
546: * @param xid the XA Transaction ID object
547: * @exception javax.transaction.xa.XAException
548: * if an error condition occurs
549: */
550: public static void xa_forget(Connection connection, int xaConId,
551: Xid xid) throws XAException {
552:
553: ConnectionJDBC2 con = (ConnectionJDBC2) connection;
554: if (con.isXaEmulation()) {
555: //
556: // Emulate xa_forget method
557: //
558: JtdsXid lxid = new JtdsXid(xid);
559: JtdsXid tran = (JtdsXid) con.getXid();
560: if (tran == null || !tran.equals(lxid)) {
561: raiseXAException(XAException.XAER_NOTA);
562: }
563: if (con.getXaState() != XA_END
564: && con.getXaState() != XA_PREPARE) {
565: // Connection not ended
566: raiseXAException(XAException.XAER_PROTO);
567: }
568: con.setXid(null);
569: try {
570: con.rollback();
571: } catch (SQLException e) {
572: raiseXAException(e);
573: } finally {
574: try {
575: con.setAutoCommit(true);
576: } catch (SQLException e) {
577: Logger
578: .println("xa_close: setAutoCommit() returned "
579: + e);
580: }
581: con.setXaState(XA_OPEN);
582: }
583: return;
584: }
585: //
586: // Execute xa_forget via MSDTC
587: //
588: int args[] = new int[5];
589: args[1] = XA_FORGET;
590: args[2] = xaConId;
591: args[3] = XA_RMID;
592: args[4] = XAResource.TMNOFLAGS;
593: try {
594: ((ConnectionJDBC2) connection).sendXaPacket(args,
595: toBytesXid(xid));
596: } catch (SQLException e) {
597: raiseXAException(e);
598: }
599: if (args[0] != XAResource.XA_OK) {
600: raiseXAException(args[0]);
601: }
602: }
603:
604: /**
605: * Construct and throw an <code>XAException</code> with an explanatory message derived from the
606: * <code>SQLException</code> and the XA error code set to <code>XAER_RMFAIL</code>.
607: *
608: * @param sqle The SQLException.
609: * @exception javax.transaction.xa.XAException
610: * exception derived from the code>SQLException</code>
611: */
612: public static void raiseXAException(SQLException sqle)
613: throws XAException {
614: XAException e = new XAException(sqle.getMessage());
615: e.errorCode = XAException.XAER_RMFAIL;
616: Logger.println("XAException: " + e.getMessage());
617: throw e;
618: }
619:
620: /**
621: * Construct and throw an <code>XAException</code> with an explanatory message and the XA error code set.
622: *
623: * @param errorCode the XA Error code
624: * @exception javax.transaction.xa.XAException
625: * the constructed exception
626: */
627: public static void raiseXAException(int errorCode)
628: throws XAException {
629: String err = "xaerunknown";
630: switch (errorCode) {
631: case XAException.XA_RBROLLBACK:
632: err = "xarbrollback";
633: break;
634: case XAException.XA_RBCOMMFAIL:
635: err = "xarbcommfail";
636: break;
637: case XAException.XA_RBDEADLOCK:
638: err = "xarbdeadlock";
639: break;
640: case XAException.XA_RBINTEGRITY:
641: err = "xarbintegrity";
642: break;
643: case XAException.XA_RBOTHER:
644: err = "xarbother";
645: break;
646: case XAException.XA_RBPROTO:
647: err = "xarbproto";
648: break;
649: case XAException.XA_RBTIMEOUT:
650: err = "xarbtimeout";
651: break;
652: case XAException.XA_RBTRANSIENT:
653: err = "xarbtransient";
654: break;
655: case XAException.XA_NOMIGRATE:
656: err = "xanomigrate";
657: break;
658: case XAException.XA_HEURHAZ:
659: err = "xaheurhaz";
660: break;
661: case XAException.XA_HEURCOM:
662: err = "xaheurcom";
663: break;
664: case XAException.XA_HEURRB:
665: err = "xaheurrb";
666: break;
667: case XAException.XA_HEURMIX:
668: err = "xaheurmix";
669: break;
670: case XAException.XA_RETRY:
671: err = "xaretry";
672: break;
673: case XAException.XA_RDONLY:
674: err = "xardonly";
675: break;
676: case XAException.XAER_ASYNC:
677: err = "xaerasync";
678: break;
679: case XAException.XAER_NOTA:
680: err = "xaernota";
681: break;
682: case XAException.XAER_INVAL:
683: err = "xaerinval";
684: break;
685: case XAException.XAER_PROTO:
686: err = "xaerproto";
687: break;
688: case XAException.XAER_RMERR:
689: err = "xaerrmerr";
690: break;
691: case XAException.XAER_RMFAIL:
692: err = "xaerrmfail";
693: break;
694: case XAException.XAER_DUPID:
695: err = "xaerdupid";
696: break;
697: case XAException.XAER_OUTSIDE:
698: err = "xaeroutside";
699: break;
700: }
701: XAException e = new XAException(Messages
702: .get("error.xaexception." + err));
703: e.errorCode = errorCode;
704: Logger.println("XAException: " + e.getMessage());
705: throw e;
706: }
707:
708: // ------------- Private methods ---------
709:
710: /**
711: * Format an XA transaction ID into a 140 byte array.
712: *
713: * @param xid the XA transaction ID
714: * @return the formatted ID as a <code>byte[]</code>
715: */
716: private static byte[] toBytesXid(Xid xid) {
717: byte[] buffer = new byte[12
718: + xid.getGlobalTransactionId().length
719: + xid.getBranchQualifier().length];
720: int fmt = xid.getFormatId();
721: buffer[0] = (byte) fmt;
722: buffer[1] = (byte) (fmt >> 8);
723: buffer[2] = (byte) (fmt >> 16);
724: buffer[3] = (byte) (fmt >> 24);
725: buffer[4] = (byte) xid.getGlobalTransactionId().length;
726: buffer[8] = (byte) xid.getBranchQualifier().length;
727: System.arraycopy(xid.getGlobalTransactionId(), 0, buffer, 12,
728: buffer[4]);
729: System.arraycopy(xid.getBranchQualifier(), 0, buffer,
730: 12 + buffer[4], buffer[8]);
731: return buffer;
732: }
733:
734: private XASupport() {
735: // Prevent an instance of this class being created.
736: }
737: }
|