/* JFox, the OpenSource J2EE Application Server
 *
 * Distributable under GNU LGPL license by gun.org
 * more details please visit http://www.huihoo.org/jfox
 */

package org.jfox.jdbc.xa;

import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.XAConnection;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.xa.XAResource;

import org.jfox.framework.logger.Logger;
import org.jfox.jdbc.ClientConnection;
import org.jfox.pool.PoolableObject;

/**
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class PoolableXAConnection implements PoolableObject, XAConnection {
    private XAConnection xaconn = null;
    private XAResource xares = null;

    // the underlying connection
    private Connection underlying = null;

    private ConnectionEventListener txConnListener = null;

    Logger logger = Logger.getLogger(PoolableXAConnection.class.getName());

    //如果 getConnection(user,pass) 与池中的 user pass 不同，则不缓存到池
    private boolean poolable = true;
    //如果在操作的时候发生了系统级错误，则该 XAConnection 将被销毁，不再缓存到池
    private boolean errored = false;

    private static TransactionManager tm = TxDataSource.getTransactionManager();
    private Synchronization txSync = new XASynchronization();

    private XAConnectionPool pool = null;

    // 标志该对象已返回到池中，即使客户保留有引用，也不能再进行操作
    private boolean passivated = true;

    private String dbUrl = null;
    private String user = null;
    private String password = null;

    private long startSleepTime = System.currentTimeMillis();


    public PoolableXAConnection(XAConnection xaconn, int transactionIsolation, String dbUrl, String user, String password) {
        this.xaconn = xaconn;
        this.dbUrl = dbUrl;
        this.user = user;
        this.password = password;
        try {
            xares = xaconn.getXAResource();
            underlying = xaconn.getConnection();
            underlying.setAutoCommit(false);
            logger.debug("set underlying connection transaction isolation: " + transactionIsolation);
            underlying.setTransactionIsolation(transactionIsolation);
        }
        catch(SQLException e) {
            throw new RuntimeException("can not init PoolableXAConnection, because getConnection failed.", e);
        }
        txConnListener = new TxConnectionListener();
        xaconn.addConnectionEventListener(txConnListener);
        txSync = new XASynchronization();
    }

    /**
     * 在返回给用户使用之前，enlist XAResource
     *
     * @throws Exception
     */
    public void activate() throws Exception {
        logger.debug("activate callback, tx status = " + tm.getStatus());
        passivated = false;
        if(tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
            Transaction tran = tm.getTransaction();
            tran.enlistResource(xares);
            tran.registerSynchronization(txSync);
            XAConnectionManager.associate(tran, this, dbUrl, user, password);
        }

        startSleepTime = System.currentTimeMillis();
    }

    /**
     * rollback 会调用 XASynchronization.afterCompletion()，解除事务和 XAConnection 的关联
     * <p/>
     * 如果还处在事务中，说明用户忘记了提交事务，那么将调用 rollback() ()
     *
     * @throws Exception
     * @see XASynchronization#afterCompletion(int)
     */
    public void passivate() throws Exception {
        logger.debug("passivate callback");
        passivated = true;
        int txStatus = tm.getStatus();
        if(txStatus != Status.STATUS_NO_TRANSACTION) {
            XAConnectionManager.disassociate(tm.getTransaction(), dbUrl, user, password);
        }

        startSleepTime = System.currentTimeMillis();
    }

    public boolean isAvailable() {
        return poolable && !errored && !passivated;
    }

    public XAResource getXAResource() throws SQLException {
        if(passivated) {
            throw new SQLException("XAConnection have returned to pool, re-retrieve it for use");
        }
        return xares;
    }

    public void close() throws SQLException {
        poolable = false;
        underlying.close();
        xaconn.close();
    }

    public Connection getConnection() throws SQLException {
        if(passivated) {
            throw new SQLException("XAConnection have returned to pool, re-retrieve it for use");
        }
        return new ClientConnection(underlying);
    }

    public void addConnectionEventListener(ConnectionEventListener listener) {
        if(!passivated) {
            xaconn.addConnectionEventListener(listener);
        }
    }

    public void removeConnectionEventListener(ConnectionEventListener listener) {
        if(!passivated) {
            xaconn.removeConnectionEventListener(listener);
        }
    }

    //标记该 PoolabeXAConnection 已经出错了，不再返回到池中
    // TODO: 出错了，是否应该调用 xaconn.close ，调用的时候会出错的
    private void markedError() {
        logger.warn("xaconn error occurred, to be marked error");
        errored = true;
    }

    /**
     * 如果使用的是特别的 user 和 password 建立的 XAConnection，
     * 将调用 notPoolable，以便不返回池
     *
     * @see XAConnectionFactory#makeObject(String, String)
     */
    void notPoolable() {
        poolable = false;
    }

    void setPool(XAConnectionPool pool) {
        this.pool = pool;
    }

    public long getSleepTime() {
        return System.currentTimeMillis() - startSleepTime;
    }

    public static void main(String[] args) {

    }

    /**
     * 完成两件事：
     * 1。在用户调用 XAConnection.close 的时候，如果处在事务中，delist 该资源
     * 2。在 XAConnection 出错的时候，标记该 PoolableXAConnection 不再返回池中
     */
    private class TxConnectionListener implements ConnectionEventListener {

        public void connectionClosed(ConnectionEvent event) {
            logger.debug("connection closed event: " + event);
            /*
             *有可能事务 commit 之后，有人再调用 conn.close()，
             * 那么 由于该 PoolableXAConnection 已经返回池中，则直接返回
             */
            if(passivated) return;
            XAConnection xaconn = (XAConnection) event.getSource();
            Transaction trans = null;
            try {
                if(tm.getStatus() != Status.STATUS_NO_TRANSACTION) {
                    trans = tm.getTransaction();
                    XAResource res = (XAResource) xaconn.getXAResource();
                    if(res != null) {
                        trans.delistResource(res, XAResource.TMSUCCESS);
                        XAConnectionManager.disassociate(trans, dbUrl, user, password);
                        // XAConnection 并没有关闭，还是有效的，所以应该返回池中
                        pool.restoreObject(PoolableXAConnection.this);
                    }
                    else {
                        logger.warn("can not getXAResource from " + xaconn);
                    }
                }
            }
            catch(Exception e) {
                throw new RuntimeException("TxConnectionListener.connectionClosed() error", e);
            }
        }

        /**
         * 标记该 PoolabeXAConnection 已经出错了，不再返回到池中
         *
         * @param event
         */
        public void connectionErrorOccurred(ConnectionEvent event) {
            // 对于 Oracle，在调用 XAConnection.close 的时候，发出 error 事件
            logger.debug("connection error occurred: " + event.getSQLException());
            PoolableXAConnection.this.markedError();
        }
    }

    class XASynchronization implements Synchronization {

        public void beforeCompletion() {

        }

        /**
         * 将 PoolableXAConnection 返回池中
         * 由 passivate 接口负责解除事务和 XAConnection 之间的关联
         *
         * @param status
         */
        public void afterCompletion(int status) {
            logger.debug("PoolableXAConnection.XASynchronization.afterCompletion " + status);
            pool.restoreObject(PoolableXAConnection.this);

        }
    }

}

