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

org.mybatis.guice.transactional.XASqlSessionManager Maven / Gradle / Ivy

Go to download

The MyBatis Guice module is easy-to-use Google Guice bridge for MyBatis sql mapping framework.

There is a newer version: 4.0.0
Show newest version
/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    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.
 */
package org.mybatis.guice.transactional;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.concurrent.ConcurrentHashMap;

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

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionManager;

public class XASqlSessionManager implements XAResource {
    private static final Log log = LogFactory.getLog(XASqlSessionManager.class);

    public static final int NO_TX = 0;
    public static final int STARTED = 1;
    public static final int ENDED = 2;
    public static final int PREPARED = 3;

    private SqlSessionManager sqlSessionManager;
    private int transactionTimeout;
    private String id;
    private Xid xid;
    private int state = NO_TX;

    private static ConcurrentHashMap globalTokens = new ConcurrentHashMap();

    public XASqlSessionManager(SqlSessionManager sqlSessionManager) {
        this.sqlSessionManager = sqlSessionManager;
        id = sqlSessionManager.getConfiguration().getEnvironment().getId();
    }

    //@Override
    public String getId() {
        return id;
    }

    public int getState() {
        return state;
    }

    private String xlatedState() {
        switch (state) {
            case NO_TX: return "NO_TX";
            case STARTED: return "STARTED";
            case ENDED: return "ENDED";
            case PREPARED: return "PREPARED";
            default: return "!invalid state (" + state + ")!";
        }
    }

    private String decodeXAResourceFlag(int flag) {
        switch(flag) {
        case XAResource.TMENDRSCAN: return "TMENDRSCAN";
        case XAResource.TMFAIL: return "TMFAIL";
        case XAResource.TMJOIN: return "TMJOIN";
        case XAResource.TMNOFLAGS: return "TMNOFLAGS";
        case XAResource.TMONEPHASE: return "TMONEPHASE";
        case XAResource.TMRESUME: return "TMRESUME";
        case XAResource.TMSTARTRSCAN: return "TMSTARTRSCAN";
        case XAResource.TMSUCCESS: return "TMSUCCESS";
        case XAResource.TMSUSPEND: return "TMSUSPEND";
        default: return "" + flag;
        }
    }

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

    //@Override
    @Override
    public boolean setTransactionTimeout(int second) throws XAException {
        transactionTimeout = second;
        return true;
    }

    //@Override
    @Override
    public void forget(Xid xid) throws XAException {
    }

    //@Override
    @Override
    public Xid[] recover(int flags) throws XAException {
        return new Xid[0];
    }

    //@Override
    @Override
    public boolean isSameRM(XAResource xares) throws XAException {
        return this == xares;
    }

    //@Override
    @Override
    public void start(Xid xid, int flag) throws XAException {
        if(log.isDebugEnabled()) log.debug(id + ": call start old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));

        if (flag != XAResource.TMNOFLAGS  && flag != XAResource.TMJOIN)
            throw new MyBatisXAException(id + ": unsupported start flag " + decodeXAResourceFlag(flag), XAException.XAER_RMERR);
        if (xid == null)
            throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);

        if (state == NO_TX) {
            if (this.xid != null)
                throw new MyBatisXAException(id + ": resource already started on XID " + this.xid, XAException.XAER_PROTO);
            else {
                if (flag == XAResource.TMJOIN)
                    throw new MyBatisXAException(id + ": resource not yet started", XAException.XAER_PROTO);
                else {
                    if (log.isDebugEnabled()) log.debug(id + ": OK to start, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
                    this.xid = xid;
                }
            }
        }
        else if (state == STARTED) {
            throw new MyBatisXAException(id + ": resource already started on XID " + this.xid, XAException.XAER_PROTO);
        }
        else if (state == ENDED) {
            if (flag == XAResource.TMNOFLAGS)
                throw new MyBatisXAException(id + ": resource already registered XID " + this.xid, XAException.XAER_DUPID);
            else {
                if (xid.equals(this.xid)) {
                    if (log.isDebugEnabled()) log.debug(id + ": OK to join, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
                }
                else
                    throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot start it on more than one XID at a time", XAException.XAER_RMERR);
            }
        }
        else if (state == PREPARED) {
            throw new MyBatisXAException(id + ": resource already prepared on XID " + this.xid, XAException.XAER_PROTO);
        }

        state = STARTED;
        parentSuspend(xid);
    }

    //@Override
    @Override
    public void end(Xid xid, int flag) throws XAException {
        if(log.isDebugEnabled()) log.debug(id + ": call end old state=" + xlatedState() + ", XID=" + xid + " and flag " + decodeXAResourceFlag(flag));

        if (flag != XAResource.TMSUCCESS && flag != XAResource.TMFAIL)
            throw new MyBatisXAException(id + ": unsupported end flag " + decodeXAResourceFlag(flag), XAException.XAER_RMERR);
        if (xid == null)
            throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);

        if (state == NO_TX) {
            throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == STARTED) {
            if (this.xid.equals(xid)) {
                if (log.isDebugEnabled()) log.debug(id + ": OK to end, old state=" + xlatedState() + ", XID=" + xid + ", flag=" + decodeXAResourceFlag(flag));
            }
            else
                throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot end it on another XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == ENDED) {
            throw new MyBatisXAException(id + ": resource already ended on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == PREPARED) {
            throw new MyBatisXAException(id + ": cannot end, resource already prepared on XID " + xid, XAException.XAER_PROTO);
        }

        if (flag == XAResource.TMFAIL) {
            // Rollback transaction. After call method end() call medod roolback()
            if (log.isDebugEnabled()) log.debug(id + ": after end TMFAIL reset state to ENDED and roolback");
        }

        this.state = ENDED;
    }

    //@Override
    @Override
    public int prepare(Xid xid) throws XAException {
        if(log.isDebugEnabled()) log.debug(id + ": call prepare old state=" + xlatedState() + ", XID=" + xid);

        if (xid == null)
            throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);

        if (state == NO_TX) {
            throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == STARTED) {
            throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == ENDED) {
            if (this.xid.equals(xid)) {
                if (log.isDebugEnabled()) log.debug(id + ": OK to prepare, old state=" + xlatedState() + ", XID=" + xid);
            }
            else
                throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot prepare it on another XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == PREPARED) {
            throw new MyBatisXAException(id + ": resource already prepared on XID " + this.xid, XAException.XAER_PROTO);
        }

        this.state = PREPARED;
        return XAResource.XA_OK;
    }

    //@Override
    @Override
    public void commit(Xid xid, boolean onePhase) throws XAException {
        if(log.isDebugEnabled()) log.debug(id + ": call commit old state=" + xlatedState() + ", XID=" + xid + " onePhase is " + onePhase);

        if (xid == null)
            throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);

        if (state == NO_TX) {
            throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == STARTED) {
            throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == ENDED) {
            if (onePhase) {
                if (log.isDebugEnabled()) log.debug(id + ": OK to commit with 1PC, old state=" + xlatedState() + ", XID=" + xid);
            }
            else
                throw new MyBatisXAException(id + ": resource never prepared on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == PREPARED) {
            if (!onePhase) {
                if (this.xid.equals(xid)) {
                    if (log.isDebugEnabled()) log.debug(id + ": OK to commit, old state=" + xlatedState() + ", XID=" + xid);
                }
                else
                    throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot commit it on another XID " + xid, XAException.XAER_PROTO);
            }
            else
                throw new MyBatisXAException(id + ": cannot commit in one phase as resource has been prepared on XID " + xid, XAException.XAER_PROTO);
        }

        try {
            parentResume(xid);
        } finally {
            if (log.isDebugEnabled()) log.debug(id + ": after commit reset state to NO_TX");
            this.state = NO_TX;
            this.xid = null;
        }
    }

    //@Override
    @Override
    public void rollback(Xid xid) throws XAException {
        if(log.isDebugEnabled()) log.debug(id + ": call roolback old state=" + xlatedState() + ", XID=" + xid);

        if (xid == null)
            throw new MyBatisXAException(id + ": XID cannot be null", XAException.XAER_INVAL);

        if (state == NO_TX) {
            throw new MyBatisXAException(id + ": resource never started on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == STARTED) {
            throw new MyBatisXAException(id + ": resource never ended on XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == ENDED) {
            if (this.xid.equals(xid)) {
                if (log.isDebugEnabled()) log.debug(id + ": OK to rollback, old state=" + xlatedState() + ", XID=" + xid);
            }
            else
                throw new MyBatisXAException(id + ": resource already started on XID " + this.xid + " - cannot roll it back on another XID " + xid, XAException.XAER_PROTO);
        }
        else if (state == PREPARED) {
            if (log.isDebugEnabled()) log.debug(id + ": rollback reset state from PREPARED to NO_TX");
            this.state = NO_TX;
            throw new MyBatisXAException(id + ": resource committed during prepare on XID " + this.xid, XAException.XA_HEURCOM);
        }

        try {
            parentResume(xid);
        } finally {
            if (log.isDebugEnabled()) log.debug(id + ": after rollback reset state to NO_TX");
            this.state = NO_TX;
            this.xid = null;
        }
    }

    private void parentSuspend(Xid xid) {
        if(log.isDebugEnabled()) {
            log.debug(id + ": suspend parent session " + xid);
        }

        byte[] trId = xid.getGlobalTransactionId();
        GlobalKey key = new GlobalKey(trId);
        GlobalToken globalToken = globalTokens.get(key);

        if(globalToken == null) {
            if(log.isDebugEnabled()) {
                log.debug(id + ": add GlobalToken " + key);
            }

            globalTokens.put(key, globalToken = new GlobalToken());
        } else {
            if(log.isDebugEnabled()) {
                log.debug(id + ": present GlobalToken " + key);
            }
        }
        globalToken.parentSuspend(id, sqlSessionManager);
    }

    private void parentResume(Xid xid) {
        if(log.isDebugEnabled()) {
            log.debug(id + ": resume parent session " + xid);
        }

        byte[] trId = xid.getGlobalTransactionId();
        GlobalKey key = new GlobalKey(trId);
        GlobalToken globalToken = globalTokens.get(key);

        if(globalToken != null) {
            globalToken.parentResume(id, sqlSessionManager);

            if(globalToken.isEmpty()) {
                if(log.isDebugEnabled()) {
                    log.debug(id + ": remove GlobalToken " + key);
                }

                globalTokens.remove(key);
            } else {
                if(log.isDebugEnabled()) {
                    log.debug(id + ": not remove GlobalToken " + key);
                }
            }
        } else {
            if(log.isDebugEnabled()) {
                log.debug(id + ": not find GlobalToken " + key);
            }
        }
    }

    static class GlobalKey {
        final byte[] globalId;
        final int arrayHash;

        public GlobalKey(byte[] globalId) {
            this.globalId = globalId;
            this.arrayHash = Arrays.hashCode(globalId);
        }

        @Override
        public int hashCode() {
            return arrayHash;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            GlobalKey other = (GlobalKey) obj;
            if (!Arrays.equals(globalId, other.globalId))
                return false;
            return true;
        }

        @Override
        public String toString() {
            StringBuilder s = new StringBuilder();
            s.append("[Xid:globalId=");
            for (int i = 0; i < globalId.length; i++) {
                s.append(Integer.toHexString(globalId[i]));
            }
            s.append(",length=").append(globalId.length);
            return s.toString();
        }
    }

    static class GlobalToken {
        private final Log log = LogFactory.getLog(getClass());
        IdentityHashMap tokens = new IdentityHashMap();

        public GlobalToken() {
        }

        void parentSuspend(String id, SqlSessionManager sqlSessionManager) {
            Token token = tokens.get(sqlSessionManager);

            if(token == null) {
                if(log.isDebugEnabled()) {
                    log.debug(id + ": add Token " + sqlSessionManager);
                }

                token = new Token(sqlSessionManager);
                tokens.put(sqlSessionManager, token);
            } else {
                if(log.isDebugEnabled()) {
                    log.debug(id + ": present Token " + sqlSessionManager);
                }
            }
            token.parentSuspend(id);
        }

        void parentResume(String id, SqlSessionManager sqlSessionManager) {
            Token token = tokens.get(sqlSessionManager);

            if(token != null) {
                token.parentResume(id);

                // remove last
                if(token.isFirst()) {
                    if(log.isDebugEnabled()) {
                        log.debug(id + ": remove parent session " + sqlSessionManager);
                    }

                    tokens.remove(sqlSessionManager);
                }
            } else {
                if(log.isDebugEnabled()) {
                    log.debug(id + ": not find parent session " + sqlSessionManager);
                }
            }
        }

        boolean isEmpty() {
            return tokens.isEmpty();
        }
    }

    static class Token {
        private final Log log = LogFactory.getLog(getClass());
        final SqlSessionManager sqlSessionManager;
        ThreadLocal localSqlSession;
        SqlSession suspendedSqlSession;
        int count;

        @SuppressWarnings("unchecked")
        public Token(SqlSessionManager sqlSessionManager) {
            this.sqlSessionManager = sqlSessionManager;
            this.count = 0;
            try {
                Field field = SqlSessionManager.class.getDeclaredField("localSqlSession");
                field.setAccessible(true);
                localSqlSession = (ThreadLocal) field.get(sqlSessionManager);
            } catch(Exception e) {
            }
        }

        boolean isFirst() {
            return count == 0;
        }

        void parentSuspend(String id) {
            if(isFirst()) {
                if(log.isDebugEnabled()) {
                    log.debug(id + " suspend parent session");
                }

                if(localSqlSession != null) {
                    suspendedSqlSession = localSqlSession.get();
                    localSqlSession.remove();
                }
            } else {
                if(log.isDebugEnabled()) {
                    log.debug(id + " skip suspend parent session");
                }
            }
            count++;
        }

        void parentResume(String id) {
            if(count > 0)
                count--;

            if(isFirst()) {
                if(log.isDebugEnabled()) {
                    log.debug(id + " resume parent session");
                }

                if(localSqlSession != null) {
                    if(suspendedSqlSession == null) {
                        localSqlSession.remove();
                    } else {
                        localSqlSession.set(suspendedSqlSession);
                        suspendedSqlSession = null;
                    }
                }
            } else {
                if(log.isDebugEnabled()) {
                    log.debug(id + " skip resume parent session");
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy