/* 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.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.Referenceable;
import javax.naming.StringRefAddr;
import javax.sql.DataSource;
import javax.sql.XAConnection;
import javax.sql.XADataSource;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.jfox.system.AbstractComponent;
import org.jfox.tm.TxManager;

/**
 * 支持事务的 DataSource，在每次 getConnection 的时候，会 enlistResource
 * work like all XADataSource's front
 *
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class TxDataSource extends AbstractComponent
        implements DataSource,
        XADataSource,
        Referenceable {
    // 注册到 XADataSourceManager 中的名字
    private String dsName = null;
    private String dbUrl = null;
    private String user = null;
    private String password = null;
    private int initNum = 2;
    private int maxRest = 10;
    private long timeout = 10 * 60 * 1000; // 10m
    private int transactionIsolation = Connection.TRANSACTION_READ_COMMITTED;

    // 缓存 XAConnection
    private XAConnectionPool pool = null;
    private static TransactionManager tm = null;

    /**
     * 生成 XAConnectionPool, used by XADataSourceManager
     * url: jdbc:protocol:subprotocol://serverName:portNumber;databaseName=db;user=x;password=y
     */
    TxDataSource(String dsName, String dbUrl, String user, String password) {
        this.dsName = dsName;
        this.dbUrl = dbUrl;
        this.user = user;
        this.password = password;
    }

    /**
     * 生成一个 TxDataSource，并不注册到 XADataSourceManager 中
     */
    TxDataSource(String dbUrl, String user, String password) {
        this.dbUrl = dbUrl;
        this.user = user;
        this.password = password;
    }

    public int getLoginTimeout() throws SQLException {
        return ((XAConnectionFactory) pool.getObjectFactory()).getLoginTimeout();
    }

    public void setLoginTimeout(int seconds) throws SQLException {
        ((XAConnectionFactory) pool.getObjectFactory()).setLoginTimeout(seconds);
    }

    public PrintWriter getLogWriter() throws SQLException {
        return ((XAConnectionFactory) pool.getObjectFactory()).getLogWriter();
    }

    public void setLogWriter(PrintWriter out) throws SQLException {
        ((XAConnectionFactory) pool.getObjectFactory()).setLogWriter(out);
    }

    public Connection getConnection() throws SQLException {
        return getConnection(user, password);
    }

    public Connection getConnection(String username, String password) throws SQLException {
        return getXAConnection(username, password).getConnection();
    }

    public XAConnection getXAConnection() throws SQLException {
        return getXAConnection(user, password);
    }

    public XAConnection getXAConnection(String user, String password) throws SQLException {
        if(pool == null) throw new SQLException("please run init() to build connection pool first.");

        //如果当前事务已经关联有 XAConnection，则直接返回
        //一个事务可能关联了几个 Connection
        int txStatus = Status.STATUS_NO_TRANSACTION;
        Transaction tran = null;
        try {
            txStatus = tm.getStatus();
            // 必须在事务环境中
            if(txStatus == Status.STATUS_NO_TRANSACTION) {
                throw new SQLException("can not getXAConnection while current thread is not in Transaction context!");
            }
            tran = tm.getTransaction();
            if(XAConnectionManager.isAssociated(tran, dbUrl, user, password)) {
                return XAConnectionManager.getXAConnection(tran, dbUrl, user, password);
            }
        }
        catch(SystemException e) {
            logger.error("getXAConnection associate current transaction error", e);
        }

        XAConnection xaconn = null;
        try {
            // 使用特别的 user 和 password，不会缓存
            if(!user.equals(this.user) || !password.equals(this.password)) {
                xaconn = (XAConnection) pool.retrieveObject(user, password);
            }
            else {
                xaconn = (PoolableXAConnection) pool.retrieveObject();
            }
        }
        catch(Exception e) {
            logger.warn("retrieve object exception ", e);
            if(e instanceof SQLException) {
                throw (SQLException) e;
            }
            else {
                throw new SQLException(e.getMessage());
            }
        }
        return xaconn;
    }

    public Reference getReference() throws NamingException {
        Reference ref = new Reference(getClass().getName(), XADataSourceObjectFactory.class.getName(), null);
        ref.add(new StringRefAddr("user", getUser()));
        ref.add(new StringRefAddr("password", getPassword()));
        ref.add(new StringRefAddr("dbURL", getDbUrl()));
        // dsName 存到 XADataSourceManager 中的名字
        ref.add(new StringRefAddr("dsName", getDsName()));
        return ref;
    }

    public String getDsName() {
        return dsName;
    }

    public String getDbUrl() {
        return dbUrl;
    }

    public String getUser() {
        return user;
    }

    public String getPassword() {
        return password;
    }

    public void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    public long getTimeout() {
        return timeout;
    }

    public int getMaxRest() {
        return maxRest;
    }

    public int getInitNum() {
        return initNum;
    }

    public void setInitNum(int initNum) {
        this.initNum = initNum;
    }

    public void setMaxRest(int maxRest) {
        this.maxRest = maxRest;
    }

    public void setTransactionIsolation(int level) {
        transactionIsolation = level;
    }

    public int getTransactionIsolation() {
        return transactionIsolation;
    }

    static TransactionManager getTransactionManager() {
        return tm;
    }

    protected void doInit() throws Exception {
        if(tm == null) {
            try {
                tm = TxManager.getInstance();
            }
            catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
        XAConnectionFactory xaConnFactory = new XAConnectionFactory(dbUrl, user, password);
        xaConnFactory.setTransactionIsolation(transactionIsolation);
        pool = new XAConnectionPool(xaConnFactory, initNum, maxRest, timeout);
        pool.init();
    }

    protected void doDestroy() throws Exception {
        pool.destroy();
        pool = null;
        XADataSourceManager.unregisterDataSource(dsName);
    }

    public static void main(String[] args) {

    }
}
