package karan.modules.hibernatetry;

import org.hibernate.*;
import org.hibernate.cfg.*;
import org.apache.log4j.*;

/**
 * Basic Hibernate helper class, handles SessionFactory, Session and Transaction.
 * <p>
 * Uses a static initializer for the initial SessionFactory creation
 * and holds Session and Transactions in thread local variables. All
 * exceptions are wrapped in an unchecked InfrastructureException.
 *
 * Hibernate startup includes building a global SessionFactory object and to store it somewhere for easy access in application code. 
 * A SessionFactory can open up new Session's. A Session represents a single-threaded unit of work, the SessionFactory is a thread-safe global object, instantiated once.
 * The HibernateUtil helper class takes care of startup and makes Session handling convenient. The so called ThreadLocal Session pattern is useful here, we keep the 
 * current unit of work associated with the current thread.
 *
 * This class does not only produce the global SessionFactory in its static initializer (called once by the JVM when the class is loaded), but also has a ThreadLocal 
 * variable to hold the Session for the current thread. No matter when you call HibernateUtil.currentSession(), it will always return the same Hibernate unit of work 
 * in the same thread. A call to HibernateUtil.closeSession()  ends the unit of work currently associated with the thread.
 *
 * This class is not necessary if you deploy Hibernate in a J2EE application server: a Session will be automatically bound to the current JTA transaction and you can 
 * look up the SessionFactory through JNDI. If you use JBoss AS, Hibernate can be deployed as a managed system service and will automatically bind the SessionFactory to 
 * a JNDI name.
 * 
 * @author christian@hibernate.org
 */
public class HibernateUtil {
    
    private static Logger log = Logger.getLogger(HibernateUtil.class);
    
    private static Configuration configuration;
    private static SessionFactory sessionFactory;
    private static final ThreadLocal threadSession = new ThreadLocal();
    private static final ThreadLocal threadTransaction = new ThreadLocal();
    
    // Create the initial SessionFactory from the default configuration files
    static {
        try {
            // Replace with Configuration() if you don't use annotations
            configuration = new Configuration();
            sessionFactory = configuration.configure().buildSessionFactory();
            // We could also let Hibernate bind it to JNDI:
            // configuration.configure().buildSessionFactory()
        } catch (Throwable ex) {
            // We have to catch Throwable, otherwise we will miss
            // NoClassDefFoundError and other subclasses of Error
            log.error("Building SessionFactory failed.", ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
    
    /**
     * Returns the SessionFactory used for this static class.
     *
     * @return SessionFactory
     */
    public static SessionFactory getSessionFactory() {
                /* Instead of a static variable, use JNDI:
                SessionFactory sessions = null;
                try {
                        javax.naming.Context ctx = new javax.naming.InitialContext();
                        String jndiName = "java:hibernate/HibernateFactory";
                        sessions = (SessionFactory)ctx.lookup(jndiName);
                } catch (javax.naming.NamingException ex) {
                        throw new InfrastructureException(ex);
                }
                return sessions;
                 */
        return sessionFactory;
    }
    
    /**
     * Returns the original Hibernate configuration.
     *
     * @return Configuration
     */
    public static Configuration getConfiguration() {
        return configuration;
    }
    
    /**
     * Rebuild the SessionFactory with the static Configuration.
     *
     */
    public static void rebuildSessionFactory() throws InfrastructureException {
        /*synchronized (sessionFactory) {
            try {
                sessionFactory = getConfiguration().buildSessionFactory();
            } catch (Exception ex) {
                throw new InfrastructureException(ex);
            }
        }*/
        rebuildSessionFactory(configuration); // this single line was the original code
    }
    
    /**
     * Rebuild the SessionFactory with the given Hibernate Configuration.
     *
     * @param cfg
     */
    public static void rebuildSessionFactory(Configuration cfg) throws InfrastructureException {
        synchronized(sessionFactory) {
            if (!sessionFactory.isClosed())
                sessionFactory.close();
            try {
                sessionFactory = cfg.buildSessionFactory();
                configuration = cfg;
            } catch(Exception ex) {
                throw new InfrastructureException(ex);
            }
        }
    }
    
    /**
     * Retrieves the current Session local to the thread.
     * <p/>
     * If no Session is open, opens a new Session for the running thread.
     *
     * @return Session
     */
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                log.debug("Opening new Session for this thread.");
                /* if (getInterceptor() != null) {
                    log.debug("Using interceptor: " + getInterceptor().getClass());
                    s = getSessionFactory().openSession(getInterceptor());
                } else { */
                    s = getSessionFactory().openSession();
                //}
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
    
    /**
     * Closes the Session local to the thread.
     */
    public static void closeSession() throws InfrastructureException {
        try {
            Session s = (Session) threadSession.get();
            threadSession.set(null);
            if (s != null && s.isOpen()) {
                log.debug("Closing Session of this thread.");
                s.close();
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
    }
    
    /**
     * Start a new database transaction.
     */
    public static void beginTransaction() throws InfrastructureException {
        Transaction tx = (Transaction) threadTransaction.get();
        try {
            if (tx == null) {
                log.debug("Starting new database transaction in this thread.");
                tx = getSession().beginTransaction();
                threadTransaction.set(tx);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
    }
    
    /**
     * Commit the database transaction.
     */
    public static void commitTransaction() throws InfrastructureException {
        Transaction tx = (Transaction) threadTransaction.get();
        try {
            if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
                log.debug("Committing database transaction of this thread.");
                tx.commit();
            }
            threadTransaction.set(null);
        } catch (RuntimeException ex) {
            log.error(ex);
            rollbackTransaction();
            throw new InfrastructureException(ex);
        }
    }
    
    /**
     * Rollback the database transaction.
     */
    public static void rollbackTransaction() throws InfrastructureException {
        Transaction tx = (Transaction) threadTransaction.get();
        try {
            threadTransaction.set(null);
            if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
                log.debug("Tyring to rollback database transaction of this thread.");
                tx.rollback();
            }
        } catch (RuntimeException ex) {
            throw new InfrastructureException("Might swallow original cause, check ERROR log!", ex);
        } finally {
            closeSession();
        }
    }
    
    /**
     * Reconnects a Hibernate Session to the current Thread.
     *
     * @param session The Hibernate Session to be reconnected.
     */
    public static void reconnect(Session session) throws InfrastructureException {
        try {
            session.reconnect();
            threadSession.set(session);
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
    }
    
    /**
     * Disconnect and return Session from current Thread.
     *
     * @return Session the disconnected Session
     */
    public static Session disconnectSession() throws InfrastructureException {
        Session session = getSession();
        try {
            threadSession.set(null);
            if (session.isConnected() && session.isOpen())
                session.disconnect();
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return session;
    }
    
    /**
     * Register a Hibernate interceptor with the current SessionFactory.
     * <p>
     * Every Session opened is opened with this interceptor after
     * registration. Has no effect if the current Session of the
     * thread is already open, effective on next close()/getSession().
     */
    public static void registerInterceptorAndRebuild(Interceptor interceptor) {
        configuration.setInterceptor(interceptor);
        rebuildSessionFactory();
    }
    
    public static Interceptor getInterceptor() {
        return configuration.getInterceptor();
    }
    
}
