package org.seasar.hibernate.impl;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;
import javax.transaction.Synchronization;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

import org.seasar.framework.util.ConnectionUtil;
import org.seasar.framework.util.DataSourceUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.TransactionManagerUtil;
import org.seasar.framework.util.TransactionUtil;
import org.seasar.hibernate.HibernateRuntimeException;
import org.seasar.hibernate.S2Session;
import org.seasar.hibernate.S2SessionFactory;

/**
 * @author higa
 *
 */
public final class S2SessionFactoryImpl
	implements S2SessionFactory, Synchronization {

	private TransactionManager transactionManager_;
	private DataSource dataSource_;
	private String configPath_ = "hibernate.cfg.xml";
	private SessionFactory sessionFactory_;
	private Map txSessions_ = new HashMap();

	public S2SessionFactoryImpl(
		TransactionManager transactionManager,
		DataSource dataSource) {

		transactionManager_ = transactionManager;
		dataSource_ = dataSource;
	}

	public TransactionManager getTransactionManager() {
		return transactionManager_;
	}

	public DataSource getDataSource() {
		return dataSource_;
	}

	public String getConfigPath() {
		return configPath_;
	}

	public void setConfigPath(String configPath) {
		configPath_ = configPath;
	}

	public SessionFactory getSessionFactory() {
		if (sessionFactory_ == null) {
			setupSessionFactory();
		}
		return sessionFactory_;
	}

	protected synchronized void setupSessionFactory() {
		if (sessionFactory_ != null) {
			return;
		}
		Configuration cfg = new Configuration();
		try {
			cfg.configure(ResourceUtil.getResource(configPath_));
			sessionFactory_ = cfg.buildSessionFactory();
		} catch (HibernateException ex) {
			throw new HibernateRuntimeException(ex);
		}
	}

	public int getTxSessionSize() {
		return txSessions_.size();
	}

	/**
	 * @see org.seasar.hibernate.S2SessionFactory#getSession()
	 */
	public S2Session getSession() {
		Transaction tx = getTransaction();
		if (tx == null) {
			return createSession();
		}
		S2Session session = (S2Session) txSessions_.get(tx);
		if (session != null && session.isOpen()) {
			return session;
		}
		return bindSession(tx);
	}

	private Transaction getTransaction() {
		return TransactionManagerUtil.getTransaction(getTransactionManager());
	}

	private S2Session createSession() {
		SessionFactory factory = getSessionFactory();
		return new S2SessionImpl(factory.openSession(getConnection()));
	}

	private Connection getConnection() {
		return DataSourceUtil.getConnection(getDataSource());
	}

	private synchronized S2Session bindSession(Transaction tx) {
		S2Session session = (S2Session) txSessions_.get(tx);
		if (session != null && session.isOpen()) {
			return session;
		}
		session = createSession();
		txSessions_.put(tx, session);
		TransactionUtil.registerSynchronization(tx, this);
		return session;
	}

	/**
	 * @see javax.transaction.Synchronization#beforeCompletion()
	 */
	public void beforeCompletion() {
		closeSession();
	}

	/**
	 * @see javax.transaction.Synchronization#afterCompletion(int)
	 */
	public void afterCompletion(int arg0) {
	}

	private void closeSession() {
		Transaction tx = getTransaction();
		if (tx != null) {
			S2Session session = null;
			synchronized (this) {
				session = (S2Session) txSessions_.remove(tx);
			}
			if (session != null && session.isOpen()) {
				try {
					session.flush();
				} finally {
					Connection con = session.close();
					ConnectionUtil.close(con);
				}
			}
		}
	}
}
