private XAReturnValue DTC_XA_Interface()

in src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAResource.java [368:744]


    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;
                            } 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;
                            }
                        }
                    } 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) {
                        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 (SQLServerException | SQLTimeoutException 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;
    }