package org.seasar.hibernate.impl;

import java.sql.Connection;
import java.util.Collections;
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.Interceptor;
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
 * @author koichikさん
 * @author うらもとさん
 * @author kenichi_okazaki
 * 
 */
public final class S2SessionFactoryImpl
	implements S2SessionFactory, Synchronization {
    private static final String DEFAULT_CONFIG_PATH = "hibernate.cfg.xml";

	private final TransactionManager transactionManager_;
	private final DataSource dataSource_;
	private String configPath_ = DEFAULT_CONFIG_PATH;
	private Interceptor interceptor_;
	private SessionFactory sessionFactory_;
	private final Map txSessions_ = Collections.synchronizedMap(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 void setInterceptor(Interceptor interceptor) {
    	interceptor_ = interceptor;
    }    
		
	public synchronized SessionFactory getSessionFactory() {
		if (sessionFactory_ != null) {
			return sessionFactory_;			
		}
		
		Configuration cfg = new Configuration();
		try {
			cfg.configure(ResourceUtil.getResource(configPath_));
			if (interceptor_ != null) {
				cfg.setInterceptor(interceptor_);
			}
			sessionFactory_ = cfg.buildSessionFactory();
		} catch (HibernateException ex) {
			throw new HibernateRuntimeException(ex);
		}
		return sessionFactory_;
	}

	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 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() {
		flushSession();
	}

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

	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.clear() ;
                } finally {
                    Connection con = session.close();
                    ConnectionUtil.close(con);
                }
            }
		}
	}
	
	private void flushSession() {
		Transaction tx = getTransaction();
		if (tx != null) {
			S2Session session = null;
			session = (S2Session) txSessions_.get(tx);
			if (session != null && session.isOpen() && !session.isReadOnly() ) {
				session.flush();
			}
		}
	}

}
