All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.SQLServerXAResource Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
/*
 * 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.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;
                            } 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.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());
                    }
                }
            }
            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;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy