Package com.taobao.metamorphosis.client.transaction

Source Code of com.taobao.metamorphosis.client.transaction.TransactionContext$EndXATransactionListener

/*
* (C) 2007-2012 Alibaba Group Holding Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Authors:
*   wuhua <wq163@163.com> , boyan <killme2008@gmail.com>
*/
package com.taobao.metamorphosis.client.transaction;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.taobao.gecko.core.command.ResponseCommand;
import com.taobao.gecko.core.command.ResponseStatus;
import com.taobao.gecko.core.util.OpaqueGenerator;
import com.taobao.gecko.service.Connection;
import com.taobao.gecko.service.RemotingClient;
import com.taobao.gecko.service.SingleRequestCallBackListener;
import com.taobao.metamorphosis.exception.MetaClientException;
import com.taobao.metamorphosis.exception.TransactionInProgressException;
import com.taobao.metamorphosis.network.BooleanCommand;
import com.taobao.metamorphosis.network.HttpStatus;
import com.taobao.metamorphosis.network.TransactionCommand;
import com.taobao.metamorphosis.transaction.LocalTransactionId;
import com.taobao.metamorphosis.transaction.TransactionId;
import com.taobao.metamorphosis.transaction.TransactionInfo;
import com.taobao.metamorphosis.transaction.XATransactionId;
import com.taobao.metamorphosis.utils.LongSequenceGenerator;
import com.taobao.metamorphosis.utils.MetaStatLog;
import com.taobao.metamorphosis.utils.StatConstants;


/**
* ���������ģ�ͬʱ֧�ֱ��������XA����
*
* @author boyan
*
*/
public class TransactionContext implements XAResource {

    private static final class EndXATransactionListener implements SingleRequestCallBackListener {
        @Override
        public void onResponse(ResponseCommand responseCommand, Connection conn) {

        }


        @Override
        public void onException(Exception e) {
            log.warn("End xa transaction failed:" + e.getMessage());
        }


        @Override
        public ThreadPoolExecutor getExecutor() {
            return null;
        }
    }

    public static final EndXATransactionListener END_XA_TX_LISTENER = new EndXATransactionListener();

    public static final Log log = LogFactory.getLog(TransactionContext.class);

    private final RemotingClient remotingClient;

    private Xid associatedXid;
    private String serverUrl;
    private TransactionId transactionId;
    private final String sessionId;
    private final LongSequenceGenerator localTransactionIdGenerator;

    private int transactionTimeout;

    private static final Log LOG = LogFactory.getLog(TransactionContext.class);

    private final TransactionSession associatedSession;

    private final long startMs;

    // Unique qualifier for XAResource.
    private String uniqueQualifier;

    // XAResource urls
    private String[] xareresourceURLs;

    private final long transactionRequestTimeoutInMills;


    public String[] getXareresourceURLs() {
        return this.xareresourceURLs;
    }


    public void setXareresourceURLs(String[] xareresourceURLs) {
        this.xareresourceURLs = xareresourceURLs;
    }


    public String getUniqueQualifier() {
        return this.uniqueQualifier;
    }


    public void setUniqueQualifier(String uniqueQualifier) {
        this.uniqueQualifier = uniqueQualifier;
    }


    public TransactionId getTransactionId() {
        return this.transactionId;
    }


    public void setServerUrl(final String serverUrl) {
        this.serverUrl = serverUrl;
    }


    public TransactionContext(final RemotingClient remotingClient, final String serverUrl,
            final TransactionSession session, final LongSequenceGenerator localTransactionIdGenerator,
            final int transactionTimeout, final long transactionRequestTimeoutInMills) {
        super();
        this.remotingClient = remotingClient;
        this.serverUrl = serverUrl;
        this.localTransactionIdGenerator = localTransactionIdGenerator;
        this.associatedSession = session;
        this.sessionId = session.getSessionId();
        this.transactionTimeout = transactionTimeout;
        this.transactionRequestTimeoutInMills = transactionRequestTimeoutInMills;
        this.startMs = System.currentTimeMillis();
    }


    private void logTxTime() {
        final long value = System.currentTimeMillis() - this.startMs;
        MetaStatLog.addStatValue2(null, StatConstants.TX_TIME, value);
    }


    public boolean isInXATransaction() {
        return this.transactionId != null && this.transactionId.isXATransaction();
    }


    public boolean isInLocalTransaction() {
        return this.transactionId != null && this.transactionId.isLocalTransaction();
    }


    public boolean isInTransaction() {
        return this.transactionId != null;
    }


    @Override
    public void commit(final Xid xid, final boolean onePhase) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Commit: " + xid);
        }

        XATransactionId x;
        if (xid == null || this.equals(this.associatedXid, xid)) {
            throw new XAException(XAException.XAER_PROTO);
        }
        else {
            x = new XATransactionId(xid, this.uniqueQualifier);
        }

        MetaStatLog.addStat(null, StatConstants.TX_COMMIT);
        this.checkConnectionConnected();
        try {

            // Notify the server that the tx was committed back
            final TransactionInfo info =
                    new TransactionInfo(x, this.sessionId, onePhase ? TransactionInfo.TransactionType.COMMIT_ONE_PHASE
                            : TransactionInfo.TransactionType.COMMIT_TWO_PHASE, this.uniqueQualifier);

            this.syncSendXATxCommand(info);
        }
        finally {
            this.associatedSession.removeContext(this);
            this.logTxTime();
        }

    }


    private void checkConnectionConnected() throws XAException {
        if (!this.remotingClient.isConnected(this.serverUrl)) {
            throw new XAException(XAException.XAER_RMFAIL);
        }
    }


    private boolean equals(final Xid xid1, final Xid xid2) {
        if (xid1 == xid2) {
            return true;
        }
        if (xid1 == null || xid2 == null) {
            return false;
        }
        return xid1.getFormatId() == xid2.getFormatId()
                && Arrays.equals(xid1.getBranchQualifier(), xid2.getBranchQualifier())
                && Arrays.equals(xid1.getGlobalTransactionId(), xid2.getGlobalTransactionId());
    }


    @Override
    public void end(final Xid xid, final int flags) throws XAException {

        if (LOG.isDebugEnabled()) {
            LOG.debug("End: " + xid);
        }

        if (this.isInLocalTransaction()) {
            throw new XAException(XAException.XAER_PROTO);
        }

        if ((flags & (TMSUSPEND | TMFAIL)) != 0) {
            // You can only suspend the associated xid.
            if (!this.equals(this.associatedXid, xid)) {
                throw new XAException(XAException.XAER_PROTO);
            }

            // TODO implement resume?
            this.setXid(null);
        }
        else if ((flags & TMSUCCESS) == TMSUCCESS) {

            if (this.equals(this.associatedXid, xid)) {
                this.setXid(null);
            }
        }
        else {
            throw new XAException(XAException.XAER_INVAL);
        }

    }


    @Override
    public void forget(final Xid xid) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Forget: " + xid);
        }

        XATransactionId x;
        if (xid == null) {
            throw new XAException(XAException.XAER_PROTO);
        }
        if (this.equals(this.associatedXid, xid)) {
            x = (XATransactionId) this.transactionId;
        }
        else {
            x = new XATransactionId(xid, this.uniqueQualifier);
        }

        final TransactionInfo info =
                new TransactionInfo(x, this.sessionId, TransactionInfo.TransactionType.FORGET, this.uniqueQualifier);
        this.syncSendXATxCommand(info);
    }


    @Override
    public int getTransactionTimeout() throws XAException {
        return this.transactionTimeout;
    }


    private String getResourceManagerId() {
        return this.serverUrl;
    }


    @Override
    public boolean isSameRM(final XAResource xaResource) throws XAException {
        if (xaResource == null) {
            return false;
        }
        if (!(xaResource instanceof TransactionContext)) {
            return false;
        }
        final TransactionContext xar = (TransactionContext) xaResource;
        try {
            return this.getResourceManagerId().equals(xar.getResourceManagerId());
        }
        catch (final Throwable e) {
            throw (XAException) new XAException("Could not get resource manager id.").initCause(e);
        }
    }


    @Override
    public int prepare(final Xid xid) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Prepare: " + xid);
        }

        XATransactionId x;

        // �����ϲ�Ӧ�ó��������������Ϊend������prepare֮ǰ���ã�associatedXid�Ѿ�����Ϊnull��Ԥ��
        if (xid == null || this.equals(this.associatedXid, xid)) {
            throw new XAException(XAException.XAER_PROTO);
        }
        else {
            x = new XATransactionId(xid, this.uniqueQualifier);
        }
        MetaStatLog.addStat(null, StatConstants.TX_PREPARE);

        final TransactionInfo info =
                new TransactionInfo(x, this.sessionId, TransactionInfo.TransactionType.PREPARE, this.uniqueQualifier);

        final BooleanCommand response = this.syncSendXATxCommand(info);
        final int result = Integer.parseInt(response.getErrorMsg());
        if (XAResource.XA_RDONLY == result) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("XA_RDONLY from prepare: " + xid);
            }
        }
        return result;

    }

    static final Pattern NEW_LINE_PATTERN = Pattern.compile("\r\n");

    static final Xid[] EMPTY_IDS = new Xid[0];


    @Override
    public Xid[] recover(final int flag) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Recover with flag: " + flag);
        }
        final TransactionInfo info =
                new TransactionInfo(null, this.sessionId, TransactionInfo.TransactionType.RECOVER, this.uniqueQualifier);
        try {
            final List<XATransactionId> xidList = new ArrayList<XATransactionId>();
            if (this.xareresourceURLs == null || this.xareresourceURLs.length == 0) {
                throw new XAException(XAException.XAER_RMFAIL);
            }
            // Recover from all XA resources.
            for (final String serverUrl : this.xareresourceURLs) {
                try {
                    final BooleanCommand receipt =
                            (BooleanCommand) this.remotingClient.invokeToGroup(serverUrl, new TransactionCommand(info,
                                OpaqueGenerator.getNextOpaque()), this.transactionRequestTimeoutInMills,
                                TimeUnit.MILLISECONDS);
                    if (receipt.getCode() != HttpStatus.Success) {
                        log.warn("Recover XAResource(" + serverUrl + ") failed,error message:" + receipt.getErrorMsg());
                        continue;
                    }
                    final String data = receipt.getErrorMsg();
                    if (StringUtils.isBlank(data)) {
                        continue;
                    }
                    final String[] xidStrs = NEW_LINE_PATTERN.split(data);
                    for (final String key : xidStrs) {
                        if (!StringUtils.isBlank(key)) {
                            xidList.add(new XATransactionId(key));
                        }
                    }
                }
                catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    throw this.toXAException(e);
                }
                catch (Exception e) {
                    log.error("Recover XAResource(" + serverUrl + ") faile", e);
                }
            }
            if (xidList.isEmpty()) {
                return EMPTY_IDS;
            }
            else {
                Xid[] rt = new Xid[xidList.size()];
                return xidList.toArray(rt);
            }
        }
        catch (final Exception e) {
            throw this.toXAException(e);
        }
    }


    @Override
    public void rollback(final Xid xid) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Rollback: " + xid);
        }

        XATransactionId x;
        if (xid == null) {
            throw new XAException(XAException.XAER_PROTO);
        }
        if (this.equals(this.associatedXid, xid)) {
            x = (XATransactionId) this.transactionId;
        }
        else {
            x = new XATransactionId(xid, this.uniqueQualifier);
        }
        MetaStatLog.addStat(null, StatConstants.TX_ROLLBACK);
        this.checkConnectionConnected();
        try {

            final TransactionInfo info =
                    new TransactionInfo(x, this.sessionId, TransactionInfo.TransactionType.ROLLBACK,
                        this.uniqueQualifier);
            this.syncSendXATxCommand(info);
        }
        finally {
            this.associatedSession.removeContext(this);
            this.logTxTime();
        }

    }

    static Pattern EXCEPTION_PAT = Pattern.compile("code=(-?\\d+),msg=(.*)");


    private BooleanCommand syncSendXATxCommand(final TransactionInfo info) throws XAException {
        try {
            final BooleanCommand resp =
                    (BooleanCommand) this.remotingClient.invokeToGroup(this.serverUrl, new TransactionCommand(info,
                        OpaqueGenerator.getNextOpaque()), this.transactionRequestTimeoutInMills, TimeUnit.MILLISECONDS);
            if (resp.getResponseStatus() != ResponseStatus.NO_ERROR) {
                final String msg = resp.getErrorMsg();
                if (msg.startsWith("XAException:")) {
                    final Matcher m = EXCEPTION_PAT.matcher(msg);
                    if (m.find()) {
                        final int code = Integer.parseInt(m.group(1));
                        final String error = m.group(2);
                        final XAException xaException = new XAException(error);
                        xaException.errorCode = code;
                        throw xaException;
                    }
                    else {
                        this.throwRMFailException(resp);
                    }

                }
                else {
                    this.throwRMFailException(resp);
                }
            }
            return resp;
        }
        catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw this.toXAException(e);
        }
        catch (final Exception e) {
            throw this.toXAException(e);
        }
    }


    private void throwRMFailException(final BooleanCommand resp) throws XAException {
        final XAException xaException = new XAException(resp.getErrorMsg());
        xaException.errorCode = XAException.XAER_RMERR;
        throw xaException;
    }


    @Override
    public boolean setTransactionTimeout(final int seconds) throws XAException {
        if (seconds < 0) {
            throw new XAException(XAException.XAER_INVAL);
        }
        this.transactionTimeout = seconds;
        return true;
    }


    /**
     * ���쳣ת��ΪXA�쳣
     *
     * @param e
     * @return
     */
    XAException toXAException(final Exception e) {
        if (e instanceof TimeoutException) {
            final XAException xae = new XAException(e.getMessage());
            xae.errorCode = XAException.XA_RBTIMEOUT;
            xae.initCause(e);
            return xae;
        }
        if (e.getCause() != null && e.getCause() instanceof XAException) {
            final XAException original = (XAException) e.getCause();
            final XAException xae = new XAException(original.getMessage());
            xae.errorCode = original.errorCode;
            xae.initCause(original);
            return xae;
        }

        if (e instanceof XAException) {
            // ((XAException) e).errorCode = XAException.XAER_RMFAIL;
            return (XAException) e;
        }

        final XAException xae = new XAException(e.getMessage());
        xae.errorCode = XAException.XAER_RMFAIL;
        xae.initCause(e);
        return xae;
    }


    private void setXid(final Xid xid) throws XAException {
        this.checkConnectionConnected();
        if (xid != null) {
            this.startXATransaction(xid);
        }
        else {
            this.endXATransaction();
        }
    }


    private void endXATransaction() throws XAException {
        if (this.transactionId != null) {
            MetaStatLog.addStat(null, StatConstants.TX_END);
            final TransactionInfo info =
                    new TransactionInfo(this.transactionId, this.sessionId, TransactionInfo.TransactionType.END,
                        this.uniqueQualifier);
            try {
                this.remotingClient.sendToGroup(this.serverUrl,
                    new TransactionCommand(info, OpaqueGenerator.getNextOpaque()), END_XA_TX_LISTENER,
                    this.transactionRequestTimeoutInMills, TimeUnit.MILLISECONDS);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Ended XA transaction: " + this.transactionId);
                }
            }
            catch (final Exception e) {
                throw this.toXAException(e);
            }
        }

        this.associatedXid = null;
        this.transactionId = null;
    }


    private void startXATransaction(final Xid xid) throws XAException {
        MetaStatLog.addStat(null, StatConstants.TX_BEGIN);
        this.associatedXid = xid;
        this.transactionId = new XATransactionId(xid, this.uniqueQualifier);

        final TransactionInfo info =
                new TransactionInfo(this.transactionId, this.sessionId, TransactionInfo.TransactionType.BEGIN,
                    this.uniqueQualifier, this.transactionTimeout);
        this.syncSendXATxCommand(info);
    }


    @Override
    public void start(final Xid xid, final int flags) throws XAException {
        if (LOG.isDebugEnabled()) {
            LOG.debug("Start: " + xid);
        }

        if ((flags & TMJOIN) == TMJOIN || (flags & TMRESUME) == TMRESUME) {
            // �������resume��xid�������뵱ǰxidһ����
            if (!this.equals(this.associatedXid, xid)) {
                throw new XAException(XAException.XAER_NOTA);
            }
        }

        if (this.isInLocalTransaction()) {
            throw new XAException(XAException.XAER_PROTO);
        }
        if (this.associatedXid != null) {
            throw new XAException(XAException.XAER_DUPID);
        }
        this.setXid(xid);
    }


    /**
     * ���з���Ϊ��������ʵ�֣�begin,commit��rollback
     */

    public void begin() throws MetaClientException {

        if (this.isInXATransaction()) {
            throw new TransactionInProgressException(
                    "Cannot start local transaction.  XA transaction is already in progress.");
        }

        if (this.transactionId == null) {
            MetaStatLog.addStat(null, StatConstants.TX_BEGIN);
            this.transactionId =
                    new LocalTransactionId(this.sessionId, this.localTransactionIdGenerator.getNextSequenceId());
            // Local transaction doesn't need unique qualifier.
            final TransactionInfo info =
                    new TransactionInfo(this.transactionId, this.sessionId, TransactionInfo.TransactionType.BEGIN,
                        this.uniqueQualifier, this.transactionTimeout);
            try {
                this.checkConnectionConnected();
                this.syncSendLocalTxCommand(info);
            }
            catch (final Exception e) {
                throw this.toMetaClientException(e);
            }
        }
    }


    private MetaClientException toMetaClientException(final Exception e) {
        if (e instanceof MetaClientException) {
            return (MetaClientException) e;
        }
        else if (e instanceof XAException) {
            return new MetaClientException(e.getMessage(), e.getCause() != null ? e.getCause() : e);
        }
        return new MetaClientException(e);
    }


    public void commit() throws MetaClientException {
        if (this.isInXATransaction()) {
            throw new TransactionInProgressException("Cannot commit() if an XA transaction is already in progress ");
        }

        // Only send commit command if the transaction was started.
        if (this.transactionId != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Commit: " + this.transactionId);

            }
            MetaStatLog.addStat(null, StatConstants.TX_COMMIT);
            try {
                // Local transaction doesn't need unique qualifier.
                final TransactionInfo info =
                        new TransactionInfo(this.transactionId, this.sessionId,
                            TransactionInfo.TransactionType.COMMIT_ONE_PHASE, this.uniqueQualifier);
                this.transactionId = null;
                this.syncSendLocalTxCommand(info);
            }
            catch (TimeoutException e) {
                throw new MetaClientException(
                    "Commit transaction timeout,the transaction state is unknown,you must check it by yourself.", e);
            }
            finally {
                this.logTxTime();
            }
        }
        else {
            throw new MetaClientException("No transaction is started");
        }
    }


    @Override
    public String toString() {
        return this.serverUrl;
    }


    private void syncSendLocalTxCommand(final TransactionInfo info) throws MetaClientException, TimeoutException {
        try {

            final BooleanCommand resp =
                    (BooleanCommand) this.remotingClient.invokeToGroup(this.serverUrl, new TransactionCommand(info,
                        OpaqueGenerator.getNextOpaque()), this.transactionRequestTimeoutInMills, TimeUnit.MILLISECONDS);
            if (resp.getResponseStatus() != ResponseStatus.NO_ERROR) {
                throw new MetaClientException(resp.getErrorMsg());
            }
        }
        catch (TimeoutException te) {
            throw te;
        }
        catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw this.toMetaClientException(e);
        }
        catch (final Exception e) {
            throw this.toMetaClientException(e);
        }
    }


    public void rollback() throws MetaClientException {
        if (this.isInXATransaction()) {
            throw new TransactionInProgressException("Cannot rollback() if an XA transaction is already in progress ");
        }

        if (this.transactionId != null) {
            MetaStatLog.addStat(null, StatConstants.TX_ROLLBACK);
            if (LOG.isDebugEnabled()) {
                LOG.debug("Rollback: " + this.transactionId);
            }
            try {
                final TransactionInfo info =
                        new TransactionInfo(this.transactionId, this.sessionId,
                            TransactionInfo.TransactionType.ROLLBACK, this.uniqueQualifier);
                this.transactionId = null;
                this.syncSendLocalTxCommand(info);
            }
            catch (TimeoutException e) {
                throw new MetaClientException(
                    "Rollback transaction timeout,the transaction state is unknown,you must check it by yourself.", e);
            }
            finally {
                this.logTxTime();
            }
        }
        else {
            throw new MetaClientException("No transaction is started");
        }

    }
}
TOP

Related Classes of com.taobao.metamorphosis.client.transaction.TransactionContext$EndXATransactionListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.