/* JFox, the OpenSource J2EE Application Server
 *
 * Copyright (C) 2002 huihoo.com
 * Distributable under GNU LGPL license
 * See the GNU Lesser General Public License for more details.
 */

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.huihoo.jfox.system.ComponentSupport;
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 ComponentSupport
                            implements DataSource,
                                        XADataSource,
                                        Referenceable {
  // 注册到 XADataSourceManager 中的名字
  private String dsName = null;
  private String dbUrl = null;
  private String user = null;
  private String password = null;
  private int initNum = 5;
  private int maxRest = 10;
  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 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);
    pool.init();
  }

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

  public static void main(String[] args) {

  }
}