com.alibaba.napoli.metamorphosis.client.transaction.TransactionContext.java Source code

Java tutorial

Introduction

Here is the source code for com.alibaba.napoli.metamorphosis.client.transaction.TransactionContext.java

Source

/*
 * (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.alibaba.napoli.metamorphosis.client.transaction;

import java.util.Arrays;
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.alibaba.napoli.gecko.core.command.ResponseStatus;
import com.alibaba.napoli.gecko.core.util.OpaqueGenerator;
import com.alibaba.napoli.gecko.service.RemotingClient;
import com.alibaba.napoli.metamorphosis.exception.MetaClientException;
import com.alibaba.napoli.metamorphosis.exception.TransactionInProgressException;
import com.alibaba.napoli.metamorphosis.network.BooleanCommand;
import com.alibaba.napoli.metamorphosis.network.HttpStatus;
import com.alibaba.napoli.metamorphosis.network.TransactionCommand;
import com.alibaba.napoli.metamorphosis.transaction.LocalTransactionId;
import com.alibaba.napoli.metamorphosis.transaction.TransactionId;
import com.alibaba.napoli.metamorphosis.transaction.TransactionInfo;
import com.alibaba.napoli.metamorphosis.transaction.XATransactionId;
import com.alibaba.napoli.metamorphosis.utils.LongSequenceGenerator;
import com.alibaba.napoli.metamorphosis.utils.MetaStatLog;
import com.alibaba.napoli.metamorphosis.utils.StatConstants;

/**
 * ??XA
 * 
 * @author boyan
 * 
 */
public class TransactionContext implements XAResource {
    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;

    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 seconds) {
        super();
        this.remotingClient = remotingClient;
        this.serverUrl = serverUrl;
        this.localTransactionIdGenerator = localTransactionIdGenerator;
        this.associatedSession = session;
        this.sessionId = session.getSessionId();
        this.transactionTimeout = seconds;
        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);
        }

        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.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);
        }

        final TransactionInfo info = new TransactionInfo(x, this.sessionId, TransactionInfo.TransactionType.FORGET);
        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;

        // ???endprepare?associatedXid?null
        if (xid == null || this.equals(this.associatedXid, xid)) {
            throw new XAException(XAException.XAER_PROTO);
        } else {
            x = new XATransactionId(xid);
        }
        MetaStatLog.addStat(null, StatConstants.TX_PREPARE);

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

        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: " + flag);
        }
        this.checkConnectionConnected();
        final TransactionInfo info = new TransactionInfo(null, this.sessionId,
                TransactionInfo.TransactionType.RECOVER);
        try {

            final BooleanCommand receipt = (BooleanCommand) this.remotingClient.invokeToGroup(this.serverUrl,
                    new TransactionCommand(info, OpaqueGenerator.getNextOpaque()));
            if (receipt.getCode() != HttpStatus.Success) {
                throw new XAException(receipt.getErrorMsg());
            }
            final String data = receipt.getErrorMsg();
            if (StringUtils.isBlank(data)) {
                return EMPTY_IDS;
            }
            final String[] xidStrs = NEW_LINE_PATTERN.split(data);

            final XATransactionId[] answer = new XATransactionId[xidStrs.length];
            int i = 0;
            for (final String key : xidStrs) {
                if (!StringUtils.isBlank(key)) {
                    answer[i++] = new XATransactionId(key);
                }
            }
            return answer;
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw this.toXAException(e);
        } 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);
        }
        MetaStatLog.addStat(null, StatConstants.TX_ROLLBACK);
        this.checkConnectionConnected();
        try {

            final TransactionInfo info = new TransactionInfo(x, this.sessionId,
                    TransactionInfo.TransactionType.ROLLBACK);
            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()));
            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.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);
            try {
                this.remotingClient.sendToGroup(this.serverUrl,
                        new TransactionCommand(info, OpaqueGenerator.getNextOpaque()));
                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);

        final TransactionInfo info = new TransactionInfo(this.transactionId, this.sessionId,
                TransactionInfo.TransactionType.BEGIN, 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) {
            // resumexid?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,commitrollback
     */

    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());
            final TransactionInfo info = new TransactionInfo(this.transactionId, this.sessionId,
                    TransactionInfo.TransactionType.BEGIN, 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 if the transaction was started.
        if (this.transactionId != null) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("Commit: " + this.transactionId);

            }
            MetaStatLog.addStat(null, StatConstants.TX_COMMIT);
            try {
                final TransactionInfo info = new TransactionInfo(this.transactionId, this.sessionId,
                        TransactionInfo.TransactionType.COMMIT_ONE_PHASE);
                this.transactionId = null;
                this.syncSendLocalTxCommand(info);
            } 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 {
        try {

            final BooleanCommand resp = (BooleanCommand) this.remotingClient.invokeToGroup(this.serverUrl,
                    new TransactionCommand(info, OpaqueGenerator.getNextOpaque()));
            if (resp.getResponseStatus() != ResponseStatus.NO_ERROR) {
                throw new MetaClientException(resp.getErrorMsg());
            }
        } 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.transactionId = null;
                this.syncSendLocalTxCommand(info);
            } finally {
                this.logTxTime();
            }
        } else {
            throw new MetaClientException("No transaction is started");
        }

    }
}