com.microsoft.sqlserver.jdbc.SQLServerXAResource Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mssql-jdbc Show documentation
Show all versions of mssql-jdbc Show documentation
Microsoft JDBC Driver for SQL Server.
/*
* 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.net.SocketException;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLTimeoutException;
import java.sql.Statement;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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;
import com.microsoft.sqlserver.jdbc.SQLServerError.TransientError;
/**
* Implements Transaction id 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;
*/
/**
* Constructs a XidImpl.
*
* @param formatId
* format id
* @param gtrid
* global id
* @param bqual
* branch id
*/
public XidImpl(int formatId, byte[] gtrid, byte[] bqual) {
this.formatId = formatId;
this.gtrid = gtrid;
this.bqual = bqual;
traceID = " XID:" + xidDisplay(this);
}
public byte[] getGlobalTransactionId() {
return gtrid;
}
public byte[] getBranchQualifier() {
return bqual;
}
public int getFormatId() {
return formatId;
}
/**
* Returns trace id 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;
}
/**
* 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 Lock xaInitLock = new ReentrantLock();
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
/**
* Used to allow the tightly coupled XA transactions, which have different XA branch transaction IDs (XIDs) but have
* the same global transaction ID (GTRID)
*/
public static final int SSTRANSTIGHTLYCPLD = 0x8000;
private SQLServerCallableStatement[] xaStatements = {null, null, null, null, null, null, null, null, null, null};
private final String traceID;
/**
* Variable that shows how many times we attempt the recovery, e.g in case of MSDTC restart
*/
private int recoveryAttempt = 0;
private final Lock lock = new ReentrantLock();
@Override
public String toString() {
return traceID;
}
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 SQLServerCallableStatement getXACallableStatementHandle(int number) throws SQLServerException {
lock.lock();
try {
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];
} finally {
lock.unlock();
}
}
private void closeXAStatements() throws SQLServerException {
lock.lock();
try {
for (int i = 0; i < xaStatements.length; i++) {
if (null != xaStatements[i]) {
xaStatements[i].close();
xaStatements[i] = null;
}
}
} finally {
lock.unlock();
}
}
final void close() throws SQLServerException {
lock.lock();
try {
try {
closeXAStatements();
} catch (Exception e) {
if (xaLogger.isLoggable(Level.WARNING))
xaLogger.warning(toString() + "Closing exception ignored: " + e);
}
if (null != controlConnection)
controlConnection.close();
} finally {
lock.unlock();
}
}
// 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;
}
}
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 {
lock.lock();
try {
if (!xaInitDone) {
try {
xaInitLock.lock();
try {
SQLServerCallableStatement initCS = null;
initCS = (SQLServerCallableStatement) controlConnection
.prepareCall("{call master..xp_sqljdbc_xa_init_ex(?, ?,?)}");
initCS.registerOutParameter(1, Types.INTEGER); // Return status
initCS.registerOutParameterNonPLP(2, Types.CHAR); // Return error message
initCS.registerOutParameterNonPLP(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;
} catch (SQLTimeoutException e4) {
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + " exception:" + e4);
throw new SQLServerException(e4.getMessage(), SQLState.STATEMENT_CANCELED,
DriverError.NOT_SET, null);
}
// 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;
}
} finally {
xaInitLock.unlock();
}
} 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;
}
} finally {
lock.unlock();
}
switch (nType) {
case XA_START:
if (!serverInfoRetrieved) {
String query = "select convert(varchar(100), SERVERPROPERTY('Edition'))as edition, "
+ " convert(varchar(100), SERVERPROPERTY('InstanceName'))as instance,"
+ " convert(varchar(100), SERVERPROPERTY('ProductVersion')) as version, @@VERSION;";
try (Statement stmt = controlConnection.createStatement();
ResultSet rs = stmt.executeQuery(query);) {
serverInfoRetrieved = true;
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.
*/
String buildInfo = rs.getString(4);
// SQL Server Linux is x64-compatible only.
if (null != buildInfo
&& (buildInfo.contains("Linux") || buildInfo.contains("Microsoft SQL Azure"))) {
architectureOS = 64;
} else if (null != buildInfo) {
architectureOS = Integer.parseInt(buildInfo.substring(buildInfo.lastIndexOf('<') + 2,
buildInfo.lastIndexOf('>')));
}
}
// 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.registerOutParameterNonPLP(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.registerOutParameterNonPLP(n++, Types.BINARY); // Returned OLE transaction cookie
cs.setInt(n++, timeoutSeconds); // Transaction timeout in seconds.
cs.setInt(n++, formatId); // Format ID
cs.registerOutParameterNonPLP(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.registerOutParameterNonPLP(n++, Types.BINARY); // Return UoW
break;
case XA_END:
sContext = "END:";
cs = getXACallableStatementHandle(XA_END);
cs.registerOutParameter(n++, Types.INTEGER);
cs.registerOutParameterNonPLP(n++, Types.CHAR);
cs.setBytes(n++, gid);
cs.setBytes(n++, bid);
cs.setInt(n++, xaFlags);
cs.setInt(n++, formatId);
cs.registerOutParameterNonPLP(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.registerOutParameterNonPLP(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.registerOutParameterNonPLP(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.registerOutParameterNonPLP(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.registerOutParameterNonPLP(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.registerOutParameterNonPLP(n++, Types.CHAR);
cs.setInt(n++, xaFlags);
cs.registerOutParameterNonPLP(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());
}
}
}
if (XA_RECOVER == nType && XA_OK != nStatus && recoveryAttempt < 1) {
// if recover failed, attempt to start again - adding the variable to check to attempt only once
// otherwise throw exception that recovery fails
// this is added since before this change, if we restart the MSDTC and attempt to do recovery, driver
// will throw exception
// "The function RECOVER: failed. The status is: -3"
recoveryAttempt++;
dtc_XA_interface(XA_START, xid, TMNOFLAGS);
return dtc_XA_interface(XA_RECOVER, xid, xaFlags);
}
// 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 (SQLTimeoutException ex) {
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + " exception:" + ex);
XAException e = new XAException(ex.toString());
e.errorCode = XAException.XAER_RMFAIL;
throw e;
} catch (SQLServerException ex) {
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + " exception:" + ex);
if (ex.getMessage().equals(SQLServerException.getErrString("R_noServerResponse"))
|| TransientError.isTransientError(ex.getSQLServerError()) || isResourceManagerFailure(ex)) {
XAException e = new XAException(ex.toString());
e.errorCode = XAException.XAER_RMFAIL;
throw e;
}
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;
}
@Override
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);
}
@Override
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);
}
@Override
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.
*/
XAReturnValue r = dtc_XA_interface(XA_PREPARE, xid, tightlyCoupled);
return r.nStatus;
}
@Override
public void commit(Xid xid, boolean onePhase) throws XAException {
dtc_XA_interface(XA_COMMIT, xid, ((onePhase) ? TMONEPHASE : TMNOFLAGS) | tightlyCoupled);
}
@Override
public void rollback(Xid xid) throws XAException {
dtc_XA_interface(XA_ROLLBACK, xid, tightlyCoupled);
}
@Override
public void forget(Xid xid) throws XAException {
dtc_XA_interface(XA_FORGET, xid, tightlyCoupled);
}
@Override
public Xid[] recover(int flags) throws XAException {
XAReturnValue r = dtc_XA_interface(XA_RECOVER, null, flags | tightlyCoupled);
int offset = 0;
ArrayList al = new ArrayList<>();
// 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;
}
try {
offset += 4;
int gidLen = (r.bData[offset++] & 0x00FF);
int bidLen = (r.bData[offset++] & 0x00FF);
byte[] gid = new byte[gidLen];
byte[] bid = new byte[bidLen];
System.arraycopy(r.bData, offset, gid, 0, gidLen);
offset += gidLen;
System.arraycopy(r.bData, offset, bid, 0, bidLen);
offset += bidLen;
XidImpl xid = new XidImpl(formatId, gid, bid);
al.add(xid);
} catch (ArrayIndexOutOfBoundsException e) {
MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_indexOutOfRange"));
Object[] msgArgs = {offset};
XAException xex = new XAException(form.format(msgArgs));
xex.errorCode = XAException.XAER_RMERR;
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + " exception:" + xex);
throw xex;
}
}
XidImpl[] xids = new XidImpl[al.size()];
for (int i = 0; i < al.size(); i++) {
xids[i] = al.get(i);
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + xids[i].toString());
}
return xids;
}
@Override
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);
}
@Override
public boolean setTransactionTimeout(int seconds) throws XAException {
isTransacrionTimeoutSet = 1;
timeoutSeconds = seconds;
if (xaLogger.isLoggable(Level.FINER))
xaLogger.finer(toString() + " TransactionTimeout:" + seconds);
return true;
}
@Override
public int getTransactionTimeout() throws XAException {
return timeoutSeconds;
}
// Returns unique id for each PooledConnection instance.
private static int nextResourceID() {
return baseResourceID.incrementAndGet();
}
private enum ResourceManagerFailure {
CONN_RESET("Connection reset"),
CONN_RESET_BY_PEER("Connection reset by peer"),
CONN_TIMEOUT("Connection timed out"),
CONN_RESILIENCY_CLIENT_UNRECOVERABLE(SQLServerException.getErrString("R_crClientUnrecoverable"));
private final String errString;
ResourceManagerFailure(String errString) {
this.errString = errString;
}
@Override
public String toString() {
return errString;
}
static ResourceManagerFailure fromString(String errString) {
for (ResourceManagerFailure resourceManagerFailure : ResourceManagerFailure.values()) {
if (errString.equalsIgnoreCase(resourceManagerFailure.toString())) {
return resourceManagerFailure;
}
}
return null;
}
}
/**
* Check if the root exception of the throwable should be a XAER_RMFAIL exception
*
* @param throwable
* The exception to check if the root cause should be a XAER_RMFAIL
*
* @return True if XAER_RMFAIL, otherwise false
*/
private boolean isResourceManagerFailure(Throwable throwable) {
Throwable root = Util.getRootCause(throwable);
if (null == root) {
return false;
}
if (xaLogger.isLoggable(Level.FINE)) {
xaLogger.fine(toString() + " Resource manager failure root exception: " + root);
}
ResourceManagerFailure err = ResourceManagerFailure.fromString(root.getMessage());
if (null == err) {
return false;
}
// Add as needed here for future XAER_RMFAIL exceptions
switch (err) {
case CONN_RESET:
case CONN_RESET_BY_PEER:
case CONN_TIMEOUT:
case CONN_RESILIENCY_CLIENT_UNRECOVERABLE:
return true;
default:
return false;
}
}
}