/* * Microsoft JDBC Driver for SQL Server * * Copyright(c) Microsoft Corporation All rights reserved. * * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. */ package com.microsoft.sqlserver.jdbc; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.text.MessageFormat; import java.util.Properties; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; import javax.transaction.xa.XAException; import javax.transaction.xa.XAResource; import javax.transaction.xa.Xid; /** * Transaction id implementation used to recover transactions. */ final class XidImpl implements Xid { private final int formatId; private final byte gtrid[]; private final byte bqual[]; private final String traceID; /* * XA Flags public static final int TMENDRSCAN = 8388608; public static final int TMFAIL = 536870912; public static final int TMJOIN = 2097152; * public static final int TMNOFLAGS = 0; public static final int TMONEPHASE = 1073741824; public static final int TMRESUME = 134217728; public * static final int TMSTARTRSCAN = 16777216; public static final int TMSUCCESS = 67108864; public static final int TMSUSPEND = 33554432; public * static final int XA_RDONLY = 3; public static final int XA_OK = 0; */ /** * Create a new XID * * @param formatId * format id * @param gtrid * global id * @param bqual * branch id */ /* L0 */ public XidImpl(int formatId, byte gtrid[], byte bqual[]) { this.formatId = formatId; this.gtrid = gtrid; this.bqual = bqual; traceID = " XID:" + xidDisplay(this); } /* L0 */ public byte[] getGlobalTransactionId() { return gtrid; } /* L0 */ public byte[] getBranchQualifier() { return bqual; } /* L0 */ public int getFormatId() { return formatId; } /** * Used for tracing * * @return traceID string */ public String toString() { return traceID; } // Returns displayable representation of xid for logging purposes. static String xidDisplay(Xid xid) { if (null == xid) return "(null)"; StringBuilder sb = new StringBuilder(300); sb.append("formatId="); sb.append(xid.getFormatId()); sb.append(" gtrid="); sb.append(Util.byteToHexDisplayString(xid.getGlobalTransactionId())); sb.append(" bqual="); sb.append(Util.byteToHexDisplayString(xid.getBranchQualifier())); return sb.toString(); } } final class XAReturnValue { int nStatus; byte bData[]; } /** * SQLServerXAResource provides an XAResource for XA distributed transaction management. XA transactions are implemented over SQL Server using * Microsoft Distributed Transaction Manager (DTC). SQLServerXAResource makes calls to a SQL Server extended dll called SQLServer_XA.dll which * interfaces with DTC. * * XA calls received by SQLServerXAResource (XA_START, XA_END, XA_PREPARE etc) are mapped to the corresponding calls to DTC functions. * * SQLServerXAResource may also be configured not to use DTC. In this case distributed transactions are simply implemented as local transactions. */ public final class SQLServerXAResource implements javax.transaction.xa.XAResource { /* * In the Java transaction API doc a 'resource manager' appears to be (for JDBC) a 'particular DBMS server that participates in distributed * transaction'. More accurately an instance of a connection to a database since commit/rollback is done at the DB connection level. A resource * adapter is the implementation below */ /* * In the JDBC XA spec the 'middle tier server' is the application server. We assume that this module implements the pooling of connections since * it must also pass the XAResouce obtained when a connection is handed to an application to the transaction manager. IE JPoolingDataSource is not * used - the JConnectionPoolDataSource and JPoolied connections are managed for pooling by the app server. */ /* Examples http://oradoc.photo.net/ora816/java.816/a81354/xadistr1.htm#1064452 */ /* * Note that EJB componenents performing getConnection() may be using the same XAConnection/XAResource since it is a pooled connection */ private int timeoutSeconds; final static int XA_START = 0; final static int XA_END = 1; final static int XA_PREPARE = 2; final static int XA_COMMIT = 3; final static int XA_ROLLBACK = 4; final static int XA_FORGET = 5; final static int XA_RECOVER = 6; final static int XA_PREPARE_EX = 7; final static int XA_ROLLBACK_EX = 8; final static int XA_FORGET_EX = 9; final static int XA_INIT = 10; private SQLServerConnection controlConnection; private SQLServerConnection con; // original connection private boolean serverInfoRetrieved; private String version, instanceName; private int ArchitectureMSSQL, ArchitectureOS; private static boolean xaInitDone; private static final Object xaInitLock; private String sResourceManagerId; private int enlistedTransactionCount; final private Logger xaLogger; static private final AtomicInteger baseResourceID = new AtomicInteger(0); // Unique id generator for each instance (used for logging). private int tightlyCoupled = 0; private int isTransacrionTimeoutSet = 0; // set to 1 if setTransactionTimeout() is called public static final int SSTRANSTIGHTLYCPLD = 0x8000; private SQLServerCallableStatement[] xaStatements = {null, null, null, null, null, null, null, null, null, null}; private final String traceID; static { xaInitLock = new Object(); } public String toString() { return traceID; } /* L0 */ SQLServerXAResource(SQLServerConnection original, SQLServerConnection control, String loginfo) { traceID = " XAResourceID:" + nextResourceID(); // Grab SQLServerXADataSource's static XA logger instance. xaLogger = SQLServerXADataSource.xaLogger; controlConnection = control; con = original; Properties p = original.activeConnectionProperties; if (p == null) sResourceManagerId = ""; else { sResourceManagerId = p.getProperty(SQLServerDriverStringProperty.SERVER_NAME.toString()) + "." + p.getProperty(SQLServerDriverStringProperty.DATABASE_NAME.toString()) + "." + p.getProperty(SQLServerDriverIntProperty.PORT_NUMBER.toString()); } if (xaLogger.isLoggable(Level.FINE)) xaLogger.fine(toString() + " created by (" + loginfo + ")"); // Information about the server, needed for XA timeout logic in the DLL. serverInfoRetrieved = false; version = "0"; instanceName = ""; ArchitectureMSSQL = 0; ArchitectureOS = 0; } private synchronized SQLServerCallableStatement getXACallableStatementHandle(int number) throws SQLServerException { assert number >= XA_START && number <= XA_FORGET_EX; assert number < xaStatements.length; if (null != xaStatements[number]) return xaStatements[number]; CallableStatement CS = null; switch (number) { case SQLServerXAResource.XA_START: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_start(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_END: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_end(?, ?, ?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_PREPARE: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare(?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_COMMIT: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_commit(?, ?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_ROLLBACK: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback(?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_FORGET: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget(?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_RECOVER: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_recover(?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_PREPARE_EX: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_prepare_ex(?, ?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_ROLLBACK_EX: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_rollback_ex(?, ?, ?, ?, ?, ?)}"); break; case SQLServerXAResource.XA_FORGET_EX: CS = controlConnection.prepareCall("{call master..xp_sqljdbc_xa_forget_ex(?, ?, ?, ?, ?, ?)}"); break; default: assert false : "Bad handle request:" + number; break; } xaStatements[number] = (SQLServerCallableStatement) CS; return xaStatements[number]; } private synchronized void closeXAStatements() throws SQLServerException { for (int i = 0; i < xaStatements.length; i++) if (null != xaStatements[i]) { xaStatements[i].close(); xaStatements[i] = null; } } final synchronized void close() throws SQLServerException { try { closeXAStatements(); } catch (Exception e) { if (xaLogger.isLoggable(Level.WARNING)) xaLogger.warning(toString() + "Closing exception ignored: " + e); } if (null != controlConnection) controlConnection.close(); } // Returns displayable representation of XID flags for logging purposes. private String flagsDisplay(int flags) { // Handle default most common case first. // Note TMNOFLAGS is 0 so this means no other bits are set. if (TMNOFLAGS == flags) return "TMNOFLAGS"; // Build displayable bitmask of rest of flags. StringBuilder sb = new StringBuilder(100); if (0 != (TMENDRSCAN & flags)) sb.append("TMENDRSCAN"); if (0 != (TMFAIL & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMFAIL"); } if (0 != (TMJOIN & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMJOIN"); } if (0 != (TMONEPHASE & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMONEPHASE"); } if (0 != (TMRESUME & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMRESUME"); } if (0 != (TMSTARTRSCAN & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMSTARTRSCAN"); } if (0 != (TMSUCCESS & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMSUCCESS"); } if (0 != (TMSUSPEND & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("TMSUSPEND"); } if (0 != (SSTRANSTIGHTLYCPLD & flags)) { if (sb.length() > 0) sb.append("|"); sb.append("SSTRANSTIGHTLYCPLD"); } return sb.toString(); } // Returns displayable representation of XID cookie for logging purposes. private String cookieDisplay(byte[] cookie) { return Util.byteToHexDisplayString(cookie); } // Returns displayable representation of XA type flag. private String typeDisplay(int type) { switch (type) { case XA_START: return "XA_START"; case XA_END: return "XA_END"; case XA_PREPARE: return "XA_PREPARE"; case XA_COMMIT: return "XA_COMMIT"; case XA_ROLLBACK: return "XA_ROLLBACK"; case XA_FORGET: return "XA_FORGET"; case XA_RECOVER: return "XA_RECOVER"; default: return "UNKNOWN" + type; } } /* L0 */ private XAReturnValue DTC_XA_Interface(int nType, Xid xid, int xaFlags) throws XAException { if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Calling XA function for type:" + typeDisplay(nType) + " flags:" + flagsDisplay(xaFlags) + " xid:" + XidImpl.xidDisplay(xid)); int formatId = 0; byte gid[] = null; byte bid[] = null; if (xid != null) { formatId = xid.getFormatId(); gid = xid.getGlobalTransactionId(); bid = xid.getBranchQualifier(); } String sContext = "DTC_XA_"; int n = 1; int nStatus = 0; XAReturnValue returnStatus = new XAReturnValue(); SQLServerCallableStatement cs = null; try { synchronized (this) { if (!xaInitDone) { try { synchronized (xaInitLock) { SQLServerCallableStatement initCS = null; initCS = (SQLServerCallableStatement) controlConnection.prepareCall("{call master..xp_sqljdbc_xa_init_ex(?, ?,?)}"); initCS.registerOutParameter(1, Types.INTEGER); // Return status initCS.registerOutParameter(2, Types.CHAR); // Return error message initCS.registerOutParameter(3, Types.CHAR); // Return version number try { initCS.execute(); } catch (SQLServerException eX) { try { initCS.close(); // Mapping between control connection and xaresource is 1:1 controlConnection.close(); } catch (SQLException e3) { // we really want to ignore this failue if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Ignoring exception when closing failed execution. exception:" + e3); } if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " exception:" + eX); throw eX; } // Check for error response from xp_sqljdbc_xa_init. int initStatus = initCS.getInt(1); String initErr = initCS.getString(2); String versionNumberXADLL = initCS.getString(3); if (xaLogger.isLoggable(Level.FINE)) xaLogger.fine(toString() + " Server XA DLL version:" + versionNumberXADLL); initCS.close(); if (XA_OK != initStatus) { assert null != initErr && initErr.length() > 1; controlConnection.close(); MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToInitializeXA")); Object[] msgArgs = {String.valueOf(initStatus), initErr}; XAException xex = new XAException(form.format(msgArgs)); xex.errorCode = initStatus; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " exception:" + xex); throw xex; } } } catch (SQLServerException e1) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToCreateXAConnection")); Object[] msgArgs = {e1.getMessage()}; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " exception:" + form.format(msgArgs)); SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } xaInitDone = true; } } switch (nType) { case XA_START: if (!serverInfoRetrieved) { try { serverInfoRetrieved = true; // data are converted to varchar as type variant returned by SERVERPROPERTY is not supported by driver String query = "select convert(varchar(100), SERVERPROPERTY('Edition'))as edition, " + " convert(varchar(100), SERVERPROPERTY('InstanceName'))as instance," + " convert(varchar(100), SERVERPROPERTY('ProductVersion')) as version," + " SUBSTRING(@@VERSION, CHARINDEX('<', @@VERSION)+2, 2)"; Statement stmt = controlConnection.createStatement(); ResultSet rs = stmt.executeQuery(query); rs.next(); String edition = rs.getString(1); ArchitectureMSSQL = ((null != edition) && (edition.contains("(64-bit)"))) ? 64 : 32; // if InstanceName is null use the default instance without name (MSSQLSERVER) instanceName = (rs.getString(2) == null) ? "MSSQLSERVER" : rs.getString(2); version = rs.getString(3); if (null == version) { version = "0"; } else if (-1 != version.indexOf('.')) { version = version.substring(0, version.indexOf('.')); } // @@VERSION returns single nvarchar string with SQL version, architecture, build date, edition and OS version // Version of the OS running MS SQL is retrieved as substring ArchitectureOS = Integer.parseInt(rs.getString(4)); rs.close(); stmt.close(); } // Got caught in static analysis. Catch only the thrown exceptions, do not catch // run time exceptions. catch (Exception e) { if (xaLogger.isLoggable(Level.WARNING)) xaLogger.warning(toString() + " Cannot retrieve server information: :" + e.getMessage()); } } sContext = "START:"; cs = getXACallableStatementHandle(XA_START); cs.registerOutParameter(n++, Types.INTEGER); // Return status cs.registerOutParameter(n++, Types.CHAR); // Return error message cs.setBytes(n++, gid); // Global XID cs.setBytes(n++, bid); // Branch ID cs.setInt(n++, xaFlags); // XA transaction flags cs.registerOutParameter(n++, Types.BINARY); // Returned OLE transaction cookie cs.setInt(n++, timeoutSeconds); // Transaction timeout in seconds. cs.setInt(n++, formatId); // Format ID cs.registerOutParameter(n++, Types.CHAR); // DLL Version number cs.setInt(n++, Integer.parseInt(version)); // Version of SQL Server cs.setInt(n++, instanceName.length()); // Length of SQL Server instance name cs.setBytes(n++, instanceName.getBytes()); // SQL Server instance name cs.setInt(n++, ArchitectureMSSQL); // Architecture of SQL Server cs.setInt(n++, ArchitectureOS); // Architecture of OS running SQL Server cs.setInt(n++, isTransacrionTimeoutSet); // pass 1 if setTransactionTimeout() is called cs.registerOutParameter(n++, Types.BINARY); // Return UoW break; case XA_END: sContext = "END:"; cs = getXACallableStatementHandle(XA_END); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); cs.setInt(n++, xaFlags); cs.setInt(n++, formatId); cs.registerOutParameter(n++, Types.BINARY); // Return UoW break; case XA_PREPARE: sContext = "PREPARE:"; if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs = getXACallableStatementHandle(XA_PREPARE_EX); else cs = getXACallableStatementHandle(XA_PREPARE); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs.setInt(n++, xaFlags); // XA transaction flags cs.setInt(n++, formatId); // Format ID n=5 for loosely coupled, n=6 for tightly coupled break; case XA_COMMIT: sContext = "COMMIT:"; cs = getXACallableStatementHandle(XA_COMMIT); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); cs.setInt(n++, xaFlags); cs.setInt(n++, formatId); break; case XA_ROLLBACK: sContext = "ROLLBACK:"; if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs = getXACallableStatementHandle(XA_ROLLBACK_EX); else cs = getXACallableStatementHandle(XA_ROLLBACK); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs.setInt(n++, xaFlags); // XA transaction flags cs.setInt(n++, formatId); // Format ID n=5 for loosely coupled, n=6 for tightly coupled break; case XA_FORGET: sContext = "FORGET:"; if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs = getXACallableStatementHandle(XA_FORGET_EX); else cs = getXACallableStatementHandle(XA_FORGET); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setBytes(n++, gid); cs.setBytes(n++, bid); if ((SSTRANSTIGHTLYCPLD & xaFlags) == SSTRANSTIGHTLYCPLD) cs.setInt(n++, xaFlags); // XA transaction flags cs.setInt(n++, formatId); // Format ID n=5 for loosely coupled, n=6 for tightly coupled break; case XA_RECOVER: sContext = "RECOVER:"; cs = getXACallableStatementHandle(XA_RECOVER); cs.registerOutParameter(n++, Types.INTEGER); cs.registerOutParameter(n++, Types.CHAR); cs.setInt(n++, xaFlags); cs.registerOutParameter(n++, Types.BINARY); // Format Id need not be sent for recover action break; default: assert false : "Unknown execution type:" + nType; break; } /* execute the interface procedure */ cs.execute(); nStatus = cs.getInt(1); String sErr = cs.getString(2); if (nType == XA_START) { String versionNumberXADLL = cs.getString(9); if (xaLogger.isLoggable(Level.FINE)) { xaLogger.fine(toString() + " Server XA DLL version:" + versionNumberXADLL); if (null != cs.getString(16)) { StringBuffer strBuf = new StringBuffer(cs.getString(16)); strBuf.insert(20, '-'); strBuf.insert(16, '-'); strBuf.insert(12, '-'); strBuf.insert(8, '-'); xaLogger.fine(toString() + " XID to UoW mapping for XA type:XA_START XID: " + XidImpl.xidDisplay(xid) + " UoW: " + strBuf.toString()); } } } if (nType == XA_END) { if (xaLogger.isLoggable(Level.FINE)) { if (null != cs.getString(7)) { StringBuffer strBuf = new StringBuffer(cs.getString(7)); strBuf.insert(20, '-'); strBuf.insert(16, '-'); strBuf.insert(12, '-'); strBuf.insert(8, '-'); xaLogger.fine( toString() + " XID to UoW mapping for XA type:XA_END XID: " + XidImpl.xidDisplay(xid) + " UoW: " + strBuf.toString()); } } } // prepare and end can return XA_RDONLY // Think should we just check for nStatus to be greater than or equal to zero instead of this check if (((XA_RDONLY == nStatus) && (XA_END != nType && XA_PREPARE != nType)) || (XA_OK != nStatus && XA_RDONLY != nStatus)) { assert (null != sErr) && (sErr.length() > 1); MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedFunctionXA")); Object[] msgArgs = {sContext, String.valueOf(nStatus), sErr}; XAException e = new XAException(form.format(msgArgs)); e.errorCode = nStatus; // if the request is end make sure we delist from the DTC transaction on rm failure. if (nType == XA_END && (XAException.XAER_RMFAIL == nStatus)) { try { if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Begin un-enlist, enlisted count:" + enlistedTransactionCount); con.JTAUnenlistConnection(); enlistedTransactionCount--; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " End un-enlist, enlisted count:" + enlistedTransactionCount); } catch (SQLServerException e1) { // ignore this message as the previous error message is more important. if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Ignoring exception:" + e1); } } throw e; } else { if (nType == XA_START) { // A physical connection may not have been enlisted yet so always enlist. byte transactionCookie[] = cs.getBytes(6); if (transactionCookie == null) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_noTransactionCookie")); Object[] msgArgs = {sContext}; SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } else { try { if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Begin enlisting, cookie:" + cookieDisplay(transactionCookie) + " enlisted count:" + enlistedTransactionCount); con.JTAEnlistConnection(transactionCookie); enlistedTransactionCount++; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " End enlisting, cookie:" + cookieDisplay(transactionCookie) + " enlisted count:" + enlistedTransactionCount); } catch (SQLServerException e1) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToEnlist")); Object[] msgArgs = {e1.getMessage()}; SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } } } if (nType == XA_END) { try { if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Begin un-enlist, enlisted count:" + enlistedTransactionCount); con.JTAUnenlistConnection(); enlistedTransactionCount--; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " End un-enlist, enlisted count:" + enlistedTransactionCount); } catch (SQLServerException e1) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToUnEnlist")); Object[] msgArgs = {e1.getMessage()}; SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } } if (nType == XA_RECOVER) { try { returnStatus.bData = cs.getBytes(4); } catch (SQLServerException e1) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToReadRecoveryXIDs")); Object[] msgArgs = {e1.getMessage()}; SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true); } } } } catch (SQLServerException ex) { if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " exception:" + ex); XAException e = new XAException(ex.toString()); e.errorCode = XAException.XAER_RMERR; throw e; } if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " Status:" + nStatus); returnStatus.nStatus = nStatus; return returnStatus; } /* L0 */ public void start(Xid xid, int flags) throws XAException { /* * Transaction mgr will use this resource in the global transaction. After this call the app server will call getConnection() to get a * connection to give the application * * The xid holds the global transaction id + the transaction branch id. The getGlobalTransactionId should be the same for each call until the * transaction is committed */ /* * XA API DOC : Start work on behalf of a transaction branch specified in xid If TMJOIN is specified, the start is for joining a transaction * previously seen by the resource manager. If TMRESUME is specified, the start is to resume a suspended transaction specified in the * parameter xid. If neither TMJOIN nor TMRESUME is specified and the transaction specified by xid has previously been seen by the resource * manager, the resource manager throws the XAException exception with XAER_DUPID error code. */ // TMNOFLAGS indicates this is the first time this physical connection has seen the transaction. // EG if the physical connection has generated multiple connection handles only work on the first // of those will be prefixed by the transaction manager with a call to start with TMNOFLAGS tightlyCoupled = flags & SSTRANSTIGHTLYCPLD; DTC_XA_Interface(XA_START, xid, flags); } /* L0 */ public void end(Xid xid, int flags) throws XAException { // Called by the transaction mgr after the app closes the connection it was given from this physical // connection /* * Ends the work performed on behalf of a transaction branch. The resource manager disassociates the XA resource from the transaction branch * specified and let the transaction be completed. If TMSUSPEND is specified in flags, the transaction branch is temporarily suspended in * incomplete state. The transaction context is in suspened state and must be resumed via start with TMRESUME specified. If TMFAIL is * specified, the portion of work has failed. The resource manager may mark the transaction as rollback-only. If TMSUCCESS is specified, the * portion of work has completed successfully. */ DTC_XA_Interface(XA_END, xid, flags | tightlyCoupled); } /* L0 */ public int prepare(Xid xid) throws XAException { /* * Ask the resource manager to prepare for a transaction commit of the transaction specified in xid. Parameters: xid - A global transaction * identifier Returns: A value indicating the resource manager's vote on the outcome of the transaction. The possible values are: XA_RDONLY or * XA_OK. If the resource manager wants to roll back the transaction, it should do so by raising an appropriate XAException in the prepare * method. */ int nStatus = XA_OK; XAReturnValue r = DTC_XA_Interface(XA_PREPARE, xid, tightlyCoupled); nStatus = r.nStatus; return nStatus; } /* L0 */ public void commit(Xid xid, boolean onePhase) throws XAException { DTC_XA_Interface(XA_COMMIT, xid, ((onePhase) ? TMONEPHASE : TMNOFLAGS) | tightlyCoupled); } /* L0 */ public void rollback(Xid xid) throws XAException { DTC_XA_Interface(XA_ROLLBACK, xid, tightlyCoupled); } /* L0 */ public void forget(Xid xid) throws XAException { DTC_XA_Interface(XA_FORGET, xid, tightlyCoupled); } /* L0 */ public Xid[] recover(int flags) throws XAException { XAReturnValue r = DTC_XA_Interface(XA_RECOVER, null, flags | tightlyCoupled); int offset = 0; Vector<XidImpl> v = new Vector<XidImpl>(); // If no XID's found, return zero length XID array (don't return null). // // Per Java 1.4.2 spec: // // The resource manager returns zero or more XIDs of the transaction branches // that are currently in a prepared or heuristically completed state. If an // error occurs during the operation, the resource manager should throw the // appropriate XAException. if (null == r.bData) return new XidImpl[0]; while (offset < r.bData.length) { int power = 1; int formatId = 0; for (int i = 0; i < 4; i++) { int x = (r.bData[offset + i] & 0x00FF); x = x * power; formatId += x; power = power * 256; } offset += 4; int gid_len = (r.bData[offset++] & 0x00FF); int bid_len = (r.bData[offset++] & 0x00FF); byte gid[] = new byte[gid_len]; byte bid[] = new byte[bid_len]; System.arraycopy(r.bData, offset, gid, 0, gid_len); offset += gid_len; System.arraycopy(r.bData, offset, bid, 0, bid_len); offset += bid_len; XidImpl xid = new XidImpl(formatId, gid, bid); v.add(xid); } XidImpl xids[] = new XidImpl[v.size()]; for (int i = 0; i < v.size(); i++) { xids[i] = v.elementAt(i); if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + xids[i].toString()); } return xids; } /* L0 */ public boolean isSameRM(XAResource xares) throws XAException { // A Resource Manager (RM) is an instance of a connection to a DB if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " xares:" + xares); // Change to return true if its the same database physical connection if (!(xares instanceof SQLServerXAResource)) return false; SQLServerXAResource jxa = (SQLServerXAResource) xares; return jxa.sResourceManagerId.equals(this.sResourceManagerId); } /* L0 */ public boolean setTransactionTimeout(int seconds) throws XAException { isTransacrionTimeoutSet = 1; timeoutSeconds = seconds; if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer(toString() + " TransactionTimeout:" + seconds); return true; } /* L0 */ public int getTransactionTimeout() throws XAException { return timeoutSeconds; } // Returns unique id for each PooledConnection instance. private static int nextResourceID() { return baseResourceID.incrementAndGet(); } }